2 * lock.c : entry point for locking RA functions for ra_serf
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
32 #include "svn_pools.h"
35 #include "../libsvn_ra/ra_loader.h"
36 #include "svn_config.h"
38 #include "svn_sorts.h"
40 #include "svn_private_config.h"
41 #include "private/svn_sorts_private.h"
47 * This enum represents the current state of our XML parsing for a REPORT.
64 typedef struct lock_ctx_t {
69 const char *token; /* For unlock */
70 svn_lock_t *lock; /* For lock */
73 svn_revnum_t revision;
75 svn_boolean_t read_headers;
77 svn_ra_serf__handler_t *handler;
79 /* The expat handler. We wrap this to do a bit more work. */
80 svn_ra_serf__response_handler_t inner_handler;
87 #define S_ SVN_XML_NAMESPACE
88 static const svn_ra_serf__xml_transition_t locks_ttable[] = {
89 /* The INITIAL state can transition into D:prop (LOCK) or
90 to D:multistatus (PROPFIND) */
91 { INITIAL, D_, "prop", PROP,
92 FALSE, { NULL }, FALSE },
94 { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
95 FALSE, { NULL }, FALSE },
97 { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
98 FALSE, { NULL }, FALSE },
101 /* ### we don't really need to parse locktype/lockscope. we know what
102 ### the values are going to be. we *could* validate that the only
103 ### possible children are D:write and D:exclusive. we'd need to
104 ### modify the state transition to tell us about all children
105 ### (ie. maybe support "*" for the name) and then validate. but it
106 ### just isn't important to validate, so disable this for now... */
108 { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
109 FALSE, { NULL }, FALSE },
111 { LOCK_TYPE, D_, "write", WRITE,
112 FALSE, { NULL }, TRUE },
114 { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
115 FALSE, { NULL }, FALSE },
117 { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
118 FALSE, { NULL }, TRUE },
121 { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
122 TRUE, { NULL }, TRUE },
124 { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
125 FALSE, { NULL }, FALSE },
127 { LOCK_TOKEN, D_, "href", HREF,
128 TRUE, { NULL }, TRUE },
130 { ACTIVE_LOCK, D_, "owner", OWNER,
131 TRUE, { NULL }, TRUE },
133 /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */
138 /* Conforms to svn_ra_serf__xml_closed_t */
140 locks_closed(svn_ra_serf__xml_estate_t *xes,
143 const svn_string_t *cdata,
145 apr_pool_t *scratch_pool)
147 lock_ctx_t *lock_ctx = baton;
149 if (leaving_state == TIMEOUT)
151 /* This function just parses the result of our own lock request,
152 so on a normal server we will only encounter 'Infinite' here. */
153 if (strcasecmp(cdata->data, "Infinite") == 0)
154 lock_ctx->lock->expiration_date = 0;
155 else if (strncasecmp(cdata->data, "Second-", 7) == 0)
158 SVN_ERR(svn_cstring_atoui(&n, cdata->data+7));
160 lock_ctx->lock->expiration_date = apr_time_now() +
161 apr_time_from_sec(n);
164 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
165 _("Invalid LOCK timeout value '%s'"),
168 else if (leaving_state == HREF)
172 char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
174 apr_collapse_spaces(buf, buf);
175 lock_ctx->lock->token = buf;
178 else if (leaving_state == OWNER)
182 lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
183 cdata->data, cdata->len);
192 set_lock_headers(serf_bucket_t *headers,
194 apr_pool_t *pool /* request pool */,
195 apr_pool_t *scratch_pool)
197 lock_ctx_t *lock_ctx = baton;
201 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
202 SVN_DAV_OPTION_LOCK_STEAL);
205 if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
207 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
208 apr_ltoa(pool, lock_ctx->revision));
214 /* Helper function for svn_ra_serf__lock and svn_ra_serf__unlock */
216 run_locks(svn_ra_serf__session_t *sess,
217 apr_array_header_t *lock_ctxs,
218 svn_boolean_t locking,
219 svn_ra_lock_callback_t lock_func,
221 apr_pool_t *scratch_pool)
223 apr_pool_t *iterpool;
224 apr_interval_time_t waittime_left = sess->timeout;
226 assert(sess->pending_error == SVN_NO_ERROR);
228 iterpool = svn_pool_create(scratch_pool);
229 while (lock_ctxs->nelts)
233 svn_pool_clear(iterpool);
235 SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
237 for (i = 0; i < lock_ctxs->nelts; i++)
239 lock_ctx_t *ctx = APR_ARRAY_IDX(lock_ctxs, i, lock_ctx_t *);
241 if (ctx->handler->done)
243 svn_error_t *server_err = NULL;
244 svn_error_t *cb_err = NULL;
247 if (ctx->handler->server_error)
248 server_err = svn_ra_serf__server_error_create(ctx->handler, iterpool);
250 /* Api users expect specific error code to detect failures,
251 pass the rest to svn_ra_serf__error_on_status */
252 switch (ctx->handler->sline.code)
256 err = NULL; /* (un)lock succeeded */
260 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_LOCK, NULL,
261 _("No lock on path '%s' (%d %s)"),
263 ctx->handler->sline.code,
264 ctx->handler->sline.reason);
267 /* ### Authz can also lead to 403. */
268 err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH,
270 _("Unlock of '%s' failed (%d %s)"),
272 ctx->handler->sline.code,
273 ctx->handler->sline.reason);
276 err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE,
278 _("Path '%s' doesn't exist in "
279 "HEAD revision (%d %s)"),
281 ctx->handler->sline.code,
282 ctx->handler->sline.reason);
285 err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
287 _("Path '%s' already locked "
290 ctx->handler->sline.code,
291 ctx->handler->sline.reason);
299 /* Handle out of date, etc by just passing the server
307 err = svn_ra_serf__unexpected_status(ctx->handler);
311 if (server_err && err && server_err->apr_err == err->apr_err)
312 err = svn_error_compose_create(server_err, err);
314 err = svn_error_compose_create(err, server_err);
317 && !SVN_ERR_IS_UNLOCK_ERROR(err)
318 && !SVN_ERR_IS_LOCK_ERROR(err))
320 /* If the error that we are going to report is just about the
321 POST unlock hook, we should first report that the operation
322 succeeded, or the repository and working copy will be
326 err->apr_err == SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED)
328 err = svn_error_compose_create(
329 err, lock_func(lock_baton, ctx->path, locking,
330 NULL, NULL, ctx->pool));
333 return svn_error_trace(err); /* Don't go through callbacks */
338 svn_lock_t *report_lock = NULL;
340 if (locking && ctx->lock->token)
341 report_lock = ctx->lock;
343 cb_err = lock_func(lock_baton, ctx->path, locking,
344 report_lock, err, ctx->pool);
346 svn_error_clear(err);
350 waittime_left = sess->timeout;
351 svn_sort__array_delete(lock_ctxs, i, 1);
354 svn_pool_destroy(ctx->pool);
359 svn_pool_destroy(iterpool);
364 /* Implements svn_ra_serf__response_handler_t */
366 handle_lock(serf_request_t *request,
367 serf_bucket_t *response,
371 lock_ctx_t *ctx = handler_baton;
373 if (!ctx->read_headers)
375 serf_bucket_t *headers;
378 headers = serf_bucket_response_get_headers(response);
380 val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
383 ctx->lock->owner = apr_pstrdup(ctx->pool, val);
386 val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
389 SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
393 ctx->read_headers = TRUE;
396 return ctx->inner_handler(request, response, ctx->inner_baton, pool);
399 /* Implements svn_ra_serf__request_body_delegate_t */
401 create_lock_body(serf_bucket_t **body_bkt,
403 serf_bucket_alloc_t *alloc,
404 apr_pool_t *pool /* request pool */,
405 apr_pool_t *scratch_pool)
407 lock_ctx_t *ctx = baton;
408 serf_bucket_t *buckets;
410 buckets = serf_bucket_aggregate_create(alloc);
412 svn_ra_serf__add_xml_header_buckets(buckets, alloc);
413 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
417 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", SVN_VA_NULL);
418 svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "exclusive", SVN_VA_NULL);
419 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
421 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", SVN_VA_NULL);
422 svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "write", SVN_VA_NULL);
423 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
425 if (ctx->lock->comment)
427 svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
431 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
438 svn_ra_serf__lock(svn_ra_session_t *ra_session,
439 apr_hash_t *path_revs,
442 svn_ra_lock_callback_t lock_func,
444 apr_pool_t *scratch_pool)
446 svn_ra_serf__session_t *session = ra_session->priv;
447 apr_hash_index_t *hi;
448 apr_pool_t *iterpool;
449 apr_array_header_t *lock_requests;
451 lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_revs),
452 sizeof(lock_ctx_t*));
454 /* ### Perhaps we should open more connections than just one? See update.c */
456 iterpool = svn_pool_create(scratch_pool);
458 for (hi = apr_hash_first(scratch_pool, path_revs);
460 hi = apr_hash_next(hi))
462 svn_ra_serf__handler_t *handler;
463 svn_ra_serf__xml_context_t *xmlctx;
465 lock_ctx_t *lock_ctx;
466 apr_pool_t *lock_pool;
468 svn_pool_clear(iterpool);
470 lock_pool = svn_pool_create(scratch_pool);
471 lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
473 lock_ctx->pool = lock_pool;
474 lock_ctx->path = apr_hash_this_key(hi);
475 lock_ctx->revision = *((svn_revnum_t*)apr_hash_this_val(hi));
476 lock_ctx->lock = svn_lock_create(lock_pool);
477 lock_ctx->lock->path = lock_ctx->path;
478 lock_ctx->lock->comment = comment;
480 lock_ctx->force = force;
481 req_url = svn_path_url_add_component2(session->session_url.path,
482 lock_ctx->path, lock_pool);
484 xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
485 NULL, locks_closed, NULL,
488 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
491 handler->method = "LOCK";
492 handler->path = req_url;
493 handler->body_type = "text/xml";
495 /* Same stupid algorithm from get_best_connection() in update.c */
496 handler->conn = session->conns[session->cur_conn];
499 if (session->cur_conn >= session->num_conns)
500 session->cur_conn = 0;
502 handler->header_delegate = set_lock_headers;
503 handler->header_delegate_baton = lock_ctx;
505 handler->body_delegate = create_lock_body;
506 handler->body_delegate_baton = lock_ctx;
508 lock_ctx->inner_handler = handler->response_handler;
509 lock_ctx->inner_baton = handler->response_baton;
510 handler->response_handler = handle_lock;
511 handler->response_baton = lock_ctx;
513 handler->no_fail_on_http_failure_status = TRUE;
515 lock_ctx->handler = handler;
517 APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
519 svn_ra_serf__request_create(handler);
522 SVN_ERR(run_locks(session, lock_requests, TRUE, lock_func, lock_baton,
525 svn_pool_destroy(iterpool);
531 set_unlock_headers(serf_bucket_t *headers,
533 apr_pool_t *pool /* request pool */,
534 apr_pool_t *scratch_pool)
536 lock_ctx_t *ctx = baton;
538 serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
541 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
542 SVN_DAV_OPTION_LOCK_BREAK);
549 svn_ra_serf__unlock(svn_ra_session_t *ra_session,
550 apr_hash_t *path_tokens,
552 svn_ra_lock_callback_t lock_func,
554 apr_pool_t *scratch_pool)
556 svn_ra_serf__session_t *session = ra_session->priv;
557 apr_hash_index_t *hi;
558 apr_pool_t *iterpool;
559 apr_array_header_t *lock_requests;
561 iterpool = svn_pool_create(scratch_pool);
563 /* If we are stealing locks we need the lock tokens */
566 /* Theoretically this part can be improved (for performance) by using
567 svn_ra_get_locks() to obtain all the locks in a single request, but
568 do we really want to improve the performance of
569 $ svn unlock --force *
572 for (hi = apr_hash_first(scratch_pool, path_tokens);
574 hi = apr_hash_next(hi))
578 svn_lock_t *existing_lock;
581 svn_pool_clear(iterpool);
583 path = apr_hash_this_key(hi);
584 token = apr_hash_this_val(hi);
586 if (token && token[0])
589 if (session->cancel_func)
590 SVN_ERR(session->cancel_func(session->cancel_baton));
592 err = svn_ra_serf__get_lock(ra_session, &existing_lock, path,
595 if (!err && existing_lock)
597 svn_hash_sets(path_tokens, path,
598 apr_pstrdup(scratch_pool, existing_lock->token));
602 err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err,
603 _("'%s' is not locked in the repository"),
609 err2 = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
610 svn_error_clear(err);
616 svn_error_clear(err);
619 svn_hash_sets(path_tokens, path, NULL);
623 /* ### Perhaps we should open more connections than just one? See update.c */
625 lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_tokens),
626 sizeof(lock_ctx_t*));
628 for (hi = apr_hash_first(scratch_pool, path_tokens);
630 hi = apr_hash_next(hi))
632 svn_ra_serf__handler_t *handler;
633 const char *req_url, *token;
634 lock_ctx_t *lock_ctx;
635 apr_pool_t *lock_pool;
637 svn_pool_clear(iterpool);
639 lock_pool = svn_pool_create(scratch_pool);
640 lock_ctx = apr_pcalloc(lock_pool, sizeof(*lock_ctx));
642 lock_ctx->pool = lock_pool;
644 lock_ctx->path = apr_hash_this_key(hi);
645 token = apr_hash_this_val(hi);
647 lock_ctx->force = force;
648 lock_ctx->token = apr_pstrcat(lock_pool, "<", token, ">", SVN_VA_NULL);
650 req_url = svn_path_url_add_component2(session->session_url.path, lock_ctx->path,
653 handler = svn_ra_serf__create_handler(session, lock_pool);
655 handler->method = "UNLOCK";
656 handler->path = req_url;
658 handler->header_delegate = set_unlock_headers;
659 handler->header_delegate_baton = lock_ctx;
661 handler->response_handler = svn_ra_serf__expect_empty_body;
662 handler->response_baton = handler;
664 handler->no_fail_on_http_failure_status = TRUE;
666 lock_ctx->handler = handler;
668 APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
670 svn_ra_serf__request_create(handler);
673 SVN_ERR(run_locks(session, lock_requests, FALSE, lock_func, lock_baton,
676 svn_pool_destroy(iterpool);