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 _("Not authorized to perform lock "
271 "operation on '%s'"),
275 err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE,
277 _("Path '%s' doesn't exist in "
278 "HEAD revision (%d %s)"),
280 ctx->handler->sline.code,
281 ctx->handler->sline.reason);
285 && SVN_ERROR_IN_CATEGORY(server_err->apr_err,
286 SVN_ERR_FS_CATEGORY_START))
291 err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
293 _("Path '%s' already locked "
296 ctx->handler->sline.code,
297 ctx->handler->sline.reason);
305 /* Handle out of date, etc by just passing the server
313 err = svn_ra_serf__unexpected_status(ctx->handler);
317 if (server_err && err && server_err->apr_err == err->apr_err)
318 err = svn_error_compose_create(server_err, err);
320 err = svn_error_compose_create(err, server_err);
323 && !SVN_ERR_IS_UNLOCK_ERROR(err)
324 && !SVN_ERR_IS_LOCK_ERROR(err))
326 /* If the error that we are going to report is just about the
327 POST unlock hook, we should first report that the operation
328 succeeded, or the repository and working copy will be
332 err->apr_err == SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED)
334 err = svn_error_compose_create(
335 err, lock_func(lock_baton, ctx->path, locking,
336 NULL, NULL, ctx->pool));
339 return svn_error_trace(err); /* Don't go through callbacks */
344 svn_lock_t *report_lock = NULL;
346 if (locking && ctx->lock->token)
347 report_lock = ctx->lock;
349 cb_err = lock_func(lock_baton, ctx->path, locking,
350 report_lock, err, ctx->pool);
352 svn_error_clear(err);
356 waittime_left = sess->timeout;
357 svn_sort__array_delete(lock_ctxs, i, 1);
360 svn_pool_destroy(ctx->pool);
365 svn_pool_destroy(iterpool);
370 /* Implements svn_ra_serf__response_handler_t */
372 handle_lock(serf_request_t *request,
373 serf_bucket_t *response,
377 lock_ctx_t *ctx = handler_baton;
379 if (!ctx->read_headers)
381 serf_bucket_t *headers;
384 headers = serf_bucket_response_get_headers(response);
386 val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
389 ctx->lock->owner = apr_pstrdup(ctx->pool, val);
392 val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
395 SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
399 ctx->read_headers = TRUE;
402 return ctx->inner_handler(request, response, ctx->inner_baton, pool);
405 /* Implements svn_ra_serf__request_body_delegate_t */
407 create_lock_body(serf_bucket_t **body_bkt,
409 serf_bucket_alloc_t *alloc,
410 apr_pool_t *pool /* request pool */,
411 apr_pool_t *scratch_pool)
413 lock_ctx_t *ctx = baton;
414 serf_bucket_t *buckets;
416 buckets = serf_bucket_aggregate_create(alloc);
418 svn_ra_serf__add_xml_header_buckets(buckets, alloc);
419 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
423 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", SVN_VA_NULL);
424 svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "exclusive", SVN_VA_NULL);
425 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
427 svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", SVN_VA_NULL);
428 svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "write", SVN_VA_NULL);
429 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
431 if (ctx->lock->comment)
433 svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
437 svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
444 svn_ra_serf__lock(svn_ra_session_t *ra_session,
445 apr_hash_t *path_revs,
448 svn_ra_lock_callback_t lock_func,
450 apr_pool_t *scratch_pool)
452 svn_ra_serf__session_t *session = ra_session->priv;
453 apr_hash_index_t *hi;
454 apr_pool_t *iterpool;
455 apr_array_header_t *lock_requests;
457 lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_revs),
458 sizeof(lock_ctx_t*));
460 /* ### Perhaps we should open more connections than just one? See update.c */
462 iterpool = svn_pool_create(scratch_pool);
464 for (hi = apr_hash_first(scratch_pool, path_revs);
466 hi = apr_hash_next(hi))
468 svn_ra_serf__handler_t *handler;
469 svn_ra_serf__xml_context_t *xmlctx;
471 lock_ctx_t *lock_ctx;
472 apr_pool_t *lock_pool;
474 svn_pool_clear(iterpool);
476 lock_pool = svn_pool_create(scratch_pool);
477 lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
479 lock_ctx->pool = lock_pool;
480 lock_ctx->path = apr_hash_this_key(hi);
481 lock_ctx->revision = *((svn_revnum_t*)apr_hash_this_val(hi));
482 lock_ctx->lock = svn_lock_create(lock_pool);
483 lock_ctx->lock->path = lock_ctx->path;
484 lock_ctx->lock->comment = comment;
486 lock_ctx->force = force;
487 req_url = svn_path_url_add_component2(session->session_url.path,
488 lock_ctx->path, lock_pool);
490 xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
491 NULL, locks_closed, NULL,
494 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
497 handler->method = "LOCK";
498 handler->path = req_url;
499 handler->body_type = "text/xml";
501 /* Same stupid algorithm from get_best_connection() in update.c */
502 handler->conn = session->conns[session->cur_conn];
505 if (session->cur_conn >= session->num_conns)
506 session->cur_conn = 0;
508 handler->header_delegate = set_lock_headers;
509 handler->header_delegate_baton = lock_ctx;
511 handler->body_delegate = create_lock_body;
512 handler->body_delegate_baton = lock_ctx;
514 lock_ctx->inner_handler = handler->response_handler;
515 lock_ctx->inner_baton = handler->response_baton;
516 handler->response_handler = handle_lock;
517 handler->response_baton = lock_ctx;
519 handler->no_fail_on_http_failure_status = TRUE;
521 lock_ctx->handler = handler;
523 APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
525 svn_ra_serf__request_create(handler);
528 SVN_ERR(run_locks(session, lock_requests, TRUE, lock_func, lock_baton,
531 svn_pool_destroy(iterpool);
537 set_unlock_headers(serf_bucket_t *headers,
539 apr_pool_t *pool /* request pool */,
540 apr_pool_t *scratch_pool)
542 lock_ctx_t *ctx = baton;
544 serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
547 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
548 SVN_DAV_OPTION_LOCK_BREAK);
555 svn_ra_serf__unlock(svn_ra_session_t *ra_session,
556 apr_hash_t *path_tokens,
558 svn_ra_lock_callback_t lock_func,
560 apr_pool_t *scratch_pool)
562 svn_ra_serf__session_t *session = ra_session->priv;
563 apr_hash_index_t *hi;
564 apr_pool_t *iterpool;
565 apr_array_header_t *lock_requests;
567 iterpool = svn_pool_create(scratch_pool);
569 /* If we are stealing locks we need the lock tokens */
572 /* Theoretically this part can be improved (for performance) by using
573 svn_ra_get_locks() to obtain all the locks in a single request, but
574 do we really want to improve the performance of
575 $ svn unlock --force *
578 for (hi = apr_hash_first(scratch_pool, path_tokens);
580 hi = apr_hash_next(hi))
584 svn_lock_t *existing_lock;
587 svn_pool_clear(iterpool);
589 path = apr_hash_this_key(hi);
590 token = apr_hash_this_val(hi);
592 if (token && token[0])
595 if (session->cancel_func)
596 SVN_ERR(session->cancel_func(session->cancel_baton));
598 err = svn_ra_serf__get_lock(ra_session, &existing_lock, path,
601 if (!err && existing_lock)
603 svn_hash_sets(path_tokens, path,
604 apr_pstrdup(scratch_pool, existing_lock->token));
608 err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err,
609 _("'%s' is not locked in the repository"),
615 err2 = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
616 svn_error_clear(err);
622 svn_error_clear(err);
625 svn_hash_sets(path_tokens, path, NULL);
629 /* ### Perhaps we should open more connections than just one? See update.c */
631 lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_tokens),
632 sizeof(lock_ctx_t*));
634 for (hi = apr_hash_first(scratch_pool, path_tokens);
636 hi = apr_hash_next(hi))
638 svn_ra_serf__handler_t *handler;
639 const char *req_url, *token;
640 lock_ctx_t *lock_ctx;
641 apr_pool_t *lock_pool;
643 svn_pool_clear(iterpool);
645 lock_pool = svn_pool_create(scratch_pool);
646 lock_ctx = apr_pcalloc(lock_pool, sizeof(*lock_ctx));
648 lock_ctx->pool = lock_pool;
650 lock_ctx->path = apr_hash_this_key(hi);
651 token = apr_hash_this_val(hi);
653 lock_ctx->force = force;
654 lock_ctx->token = apr_pstrcat(lock_pool, "<", token, ">", SVN_VA_NULL);
656 req_url = svn_path_url_add_component2(session->session_url.path, lock_ctx->path,
659 handler = svn_ra_serf__create_handler(session, lock_pool);
661 handler->method = "UNLOCK";
662 handler->path = req_url;
664 handler->header_delegate = set_unlock_headers;
665 handler->header_delegate_baton = lock_ctx;
667 handler->response_handler = svn_ra_serf__expect_empty_body;
668 handler->response_baton = handler;
670 handler->no_fail_on_http_failure_status = TRUE;
672 lock_ctx->handler = handler;
674 APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
676 svn_ra_serf__request_create(handler);
679 SVN_ERR(run_locks(session, lock_requests, FALSE, lock_func, lock_baton,
682 svn_pool_destroy(iterpool);