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);