]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_serf/lock.c
Import lib9p 7ddb1164407da19b9b1afb83df83ae65a71a9a66.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_ra_serf / lock.c
1 /*
2  * lock.c :  entry point for locking RA functions for ra_serf
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26 #include <apr_uri.h>
27 #include <serf.h>
28 #include <assert.h>
29
30 #include "svn_dav.h"
31 #include "svn_hash.h"
32 #include "svn_pools.h"
33 #include "svn_ra.h"
34
35 #include "../libsvn_ra/ra_loader.h"
36 #include "svn_config.h"
37 #include "svn_path.h"
38 #include "svn_sorts.h"
39 #include "svn_time.h"
40 #include "svn_private_config.h"
41 #include "private/svn_sorts_private.h"
42
43 #include "ra_serf.h"
44
45 \f
46 /*
47  * This enum represents the current state of our XML parsing for a REPORT.
48  */
49 enum {
50   INITIAL = 0,
51   PROP,
52   LOCK_DISCOVERY,
53   ACTIVE_LOCK,
54   LOCK_TYPE,
55   LOCK_SCOPE,
56   DEPTH,
57   TIMEOUT,
58   LOCK_TOKEN,
59   OWNER,
60   HREF
61 };
62
63
64 typedef struct lock_ctx_t {
65   apr_pool_t *pool;
66
67   const char *path;
68
69   const char *token; /* For unlock */
70   svn_lock_t *lock; /* For lock */
71
72   svn_boolean_t force;
73   svn_revnum_t revision;
74
75   svn_boolean_t read_headers;
76
77   svn_ra_serf__handler_t *handler;
78
79   /* The expat handler. We wrap this to do a bit more work.  */
80   svn_ra_serf__response_handler_t inner_handler;
81   void *inner_baton;
82
83 } lock_ctx_t;
84
85
86 #define D_ "DAV:"
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 },
93
94   { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
95     FALSE, { NULL }, FALSE },
96
97   { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
98     FALSE, { NULL }, FALSE },
99
100 #if 0
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... */
107
108   { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
109     FALSE, { NULL }, FALSE },
110
111   { LOCK_TYPE, D_, "write", WRITE,
112     FALSE, { NULL }, TRUE },
113
114   { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
115     FALSE, { NULL }, FALSE },
116
117   { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
118     FALSE, { NULL }, TRUE },
119 #endif /* 0  */
120
121   { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
122     TRUE, { NULL }, TRUE },
123
124   { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
125     FALSE, { NULL }, FALSE },
126
127   { LOCK_TOKEN, D_, "href", HREF,
128     TRUE, { NULL }, TRUE },
129
130   { ACTIVE_LOCK, D_, "owner", OWNER,
131     TRUE, { NULL }, TRUE },
132
133   /* ACTIVE_LOCK has a D:depth child, but we can ignore that.  */
134
135   { 0 }
136 };
137
138 /* Conforms to svn_ra_serf__xml_closed_t  */
139 static svn_error_t *
140 locks_closed(svn_ra_serf__xml_estate_t *xes,
141              void *baton,
142              int leaving_state,
143              const svn_string_t *cdata,
144              apr_hash_t *attrs,
145              apr_pool_t *scratch_pool)
146 {
147   lock_ctx_t *lock_ctx = baton;
148
149   if (leaving_state == TIMEOUT)
150     {
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)
156         {
157           unsigned n;
158           SVN_ERR(svn_cstring_atoui(&n, cdata->data+7));
159
160           lock_ctx->lock->expiration_date = apr_time_now() +
161                                             apr_time_from_sec(n);
162         }
163       else
164          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
165                                   _("Invalid LOCK timeout value '%s'"),
166                                   cdata->data);
167     }
168   else if (leaving_state == HREF)
169     {
170       if (cdata->len)
171         {
172           char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
173
174           apr_collapse_spaces(buf, buf);
175           lock_ctx->lock->token = buf;
176         }
177     }
178   else if (leaving_state == OWNER)
179     {
180       if (cdata->len)
181         {
182           lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
183                                                    cdata->data, cdata->len);
184         }
185     }
186
187   return SVN_NO_ERROR;
188 }
189
190
191 static svn_error_t *
192 set_lock_headers(serf_bucket_t *headers,
193                  void *baton,
194                  apr_pool_t *pool /* request pool */,
195                  apr_pool_t *scratch_pool)
196 {
197   lock_ctx_t *lock_ctx = baton;
198
199   if (lock_ctx->force)
200     {
201       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
202                               SVN_DAV_OPTION_LOCK_STEAL);
203     }
204
205   if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
206     {
207       serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
208                               apr_ltoa(pool, lock_ctx->revision));
209     }
210
211   return APR_SUCCESS;
212 }
213
214 /* Helper function for svn_ra_serf__lock and svn_ra_serf__unlock */
215 static svn_error_t *
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,
220           void *lock_baton,
221           apr_pool_t *scratch_pool)
222 {
223   apr_pool_t *iterpool;
224   apr_interval_time_t waittime_left = sess->timeout;
225
226   assert(sess->pending_error == SVN_NO_ERROR);
227
228   iterpool = svn_pool_create(scratch_pool);
229   while (lock_ctxs->nelts)
230     {
231       int i;
232
233       svn_pool_clear(iterpool);
234
235       SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
236
237       for (i = 0; i < lock_ctxs->nelts; i++)
238         {
239           lock_ctx_t *ctx = APR_ARRAY_IDX(lock_ctxs, i, lock_ctx_t *);
240
241           if (ctx->handler->done)
242             {
243               svn_error_t *server_err = NULL;
244               svn_error_t *cb_err = NULL;
245               svn_error_t *err;
246
247               if (ctx->handler->server_error)
248                 server_err = svn_ra_serf__server_error_create(ctx->handler, iterpool);
249
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)
253                 {
254                   case 200:
255                   case 204:
256                     err = NULL; /* (un)lock succeeded */
257                     break;
258
259                   case 400:
260                     err = svn_error_createf(SVN_ERR_FS_NO_SUCH_LOCK, NULL,
261                                             _("No lock on path '%s' (%d %s)"),
262                                             ctx->path,
263                                             ctx->handler->sline.code,
264                                             ctx->handler->sline.reason);
265                     break;
266                   case 403:
267                     /* ### Authz can also lead to 403. */
268                     err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH,
269                                             NULL,
270                                             _("Not authorized to perform lock "
271                                               "operation on '%s'"),
272                                             ctx->path);
273                     break;
274                   case 405:
275                     err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE,
276                                             NULL,
277                                             _("Path '%s' doesn't exist in "
278                                               "HEAD revision (%d %s)"),
279                                             ctx->path,
280                                             ctx->handler->sline.code,
281                                             ctx->handler->sline.reason);
282                     break;
283                   case 423:
284                     if (server_err
285                         && SVN_ERROR_IN_CATEGORY(server_err->apr_err,
286                                                  SVN_ERR_FS_CATEGORY_START))
287                       {
288                         err = NULL;
289                       }
290                     else
291                       err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
292                                               NULL,
293                                               _("Path '%s' already locked "
294                                                 "(%d %s)"),
295                                               ctx->path,
296                                               ctx->handler->sline.code,
297                                               ctx->handler->sline.reason);
298                     break;
299
300                   case 404:
301                   case 409:
302                   case 500:
303                     if (server_err)
304                       {
305                         /* Handle out of date, etc by just passing the server
306                            error */
307                         err = NULL;
308                         break;
309                       }
310
311                     /* Fall through */
312                   default:
313                     err = svn_ra_serf__unexpected_status(ctx->handler);
314                     break;
315                 }
316
317               if (server_err && err && server_err->apr_err == err->apr_err)
318                 err = svn_error_compose_create(server_err, err);
319               else
320                 err = svn_error_compose_create(err, server_err);
321
322               if (err
323                   && !SVN_ERR_IS_UNLOCK_ERROR(err)
324                   && !SVN_ERR_IS_LOCK_ERROR(err))
325                 {
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
329                      out of sync... */
330
331                   if (lock_func &&
332                       err->apr_err == SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED)
333                     {
334                       err = svn_error_compose_create(
335                                   err, lock_func(lock_baton, ctx->path, locking,
336                                                  NULL, NULL, ctx->pool));
337                     }
338
339                   return svn_error_trace(err); /* Don't go through callbacks */
340                 }
341
342               if (lock_func)
343                 {
344                   svn_lock_t *report_lock = NULL;
345
346                   if (locking && ctx->lock->token)
347                     report_lock = ctx->lock;
348
349                   cb_err = lock_func(lock_baton, ctx->path, locking,
350                                      report_lock, err, ctx->pool);
351                 }
352               svn_error_clear(err);
353
354               SVN_ERR(cb_err);
355
356               waittime_left = sess->timeout;
357               svn_sort__array_delete(lock_ctxs, i, 1);
358               i--;
359
360               svn_pool_destroy(ctx->pool);
361               continue;
362             }
363         }
364     }
365   svn_pool_destroy(iterpool);
366
367   return SVN_NO_ERROR;
368 }
369
370 /* Implements svn_ra_serf__response_handler_t */
371 static svn_error_t *
372 handle_lock(serf_request_t *request,
373             serf_bucket_t *response,
374             void *handler_baton,
375             apr_pool_t *pool)
376 {
377   lock_ctx_t *ctx = handler_baton;
378
379   if (!ctx->read_headers)
380     {
381       serf_bucket_t *headers;
382       const char *val;
383
384       headers = serf_bucket_response_get_headers(response);
385
386       val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
387       if (val)
388         {
389           ctx->lock->owner = apr_pstrdup(ctx->pool, val);
390         }
391
392       val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
393       if (val)
394         {
395           SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
396                                         ctx->pool));
397         }
398
399       ctx->read_headers = TRUE;
400     }
401
402   return ctx->inner_handler(request, response, ctx->inner_baton, pool);
403 }
404
405 /* Implements svn_ra_serf__request_body_delegate_t */
406 static svn_error_t *
407 create_lock_body(serf_bucket_t **body_bkt,
408                  void *baton,
409                  serf_bucket_alloc_t *alloc,
410                  apr_pool_t *pool /* request pool */,
411                  apr_pool_t *scratch_pool)
412 {
413   lock_ctx_t *ctx = baton;
414   serf_bucket_t *buckets;
415
416   buckets = serf_bucket_aggregate_create(alloc);
417
418   svn_ra_serf__add_xml_header_buckets(buckets, alloc);
419   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
420                                     "xmlns", "DAV:",
421                                     SVN_VA_NULL);
422
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");
426
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");
430
431   if (ctx->lock->comment)
432     {
433       svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
434                                    alloc);
435     }
436
437   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
438
439   *body_bkt = buckets;
440   return SVN_NO_ERROR;
441 }
442
443 svn_error_t *
444 svn_ra_serf__lock(svn_ra_session_t *ra_session,
445                   apr_hash_t *path_revs,
446                   const char *comment,
447                   svn_boolean_t force,
448                   svn_ra_lock_callback_t lock_func,
449                   void *lock_baton,
450                   apr_pool_t *scratch_pool)
451 {
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;
456
457   lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_revs),
458                                  sizeof(lock_ctx_t*));
459
460   /* ### Perhaps we should open more connections than just one? See update.c */
461
462   iterpool = svn_pool_create(scratch_pool);
463
464   for (hi = apr_hash_first(scratch_pool, path_revs);
465        hi;
466        hi = apr_hash_next(hi))
467     {
468       svn_ra_serf__handler_t *handler;
469       svn_ra_serf__xml_context_t *xmlctx;
470       const char *req_url;
471       lock_ctx_t *lock_ctx;
472       apr_pool_t *lock_pool;
473
474       svn_pool_clear(iterpool);
475
476       lock_pool = svn_pool_create(scratch_pool);
477       lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
478
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;
485
486       lock_ctx->force = force;
487       req_url = svn_path_url_add_component2(session->session_url.path,
488                                             lock_ctx->path, lock_pool);
489
490       xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
491                                                NULL, locks_closed, NULL,
492                                                lock_ctx,
493                                                lock_pool);
494       handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
495                                                   lock_pool);
496
497       handler->method = "LOCK";
498       handler->path = req_url;
499       handler->body_type = "text/xml";
500
501       /* Same stupid algorithm from get_best_connection() in update.c */
502       handler->conn = session->conns[session->cur_conn];
503       session->cur_conn++;
504
505       if (session->cur_conn >= session->num_conns)
506         session->cur_conn = 0;
507
508       handler->header_delegate = set_lock_headers;
509       handler->header_delegate_baton = lock_ctx;
510
511       handler->body_delegate = create_lock_body;
512       handler->body_delegate_baton = lock_ctx;
513
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;
518
519       handler->no_fail_on_http_failure_status = TRUE;
520
521       lock_ctx->handler = handler;
522
523       APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
524
525       svn_ra_serf__request_create(handler);
526     }
527
528   SVN_ERR(run_locks(session, lock_requests, TRUE, lock_func, lock_baton,
529                     iterpool));
530
531   svn_pool_destroy(iterpool);
532
533   return SVN_NO_ERROR;
534 }
535
536 static svn_error_t *
537 set_unlock_headers(serf_bucket_t *headers,
538                    void *baton,
539                    apr_pool_t *pool /* request pool */,
540                    apr_pool_t *scratch_pool)
541 {
542   lock_ctx_t *ctx = baton;
543
544   serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
545   if (ctx->force)
546     {
547       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
548                               SVN_DAV_OPTION_LOCK_BREAK);
549     }
550
551   return SVN_NO_ERROR;
552 }
553
554 svn_error_t *
555 svn_ra_serf__unlock(svn_ra_session_t *ra_session,
556                     apr_hash_t *path_tokens,
557                     svn_boolean_t force,
558                     svn_ra_lock_callback_t lock_func,
559                     void *lock_baton,
560                     apr_pool_t *scratch_pool)
561 {
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;
566
567   iterpool = svn_pool_create(scratch_pool);
568
569   /* If we are stealing locks we need the lock tokens */
570   if (force)
571     {
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 *
576        */
577
578       for (hi = apr_hash_first(scratch_pool, path_tokens);
579        hi;
580        hi = apr_hash_next(hi))
581         {
582           const char *path;
583           const char *token;
584           svn_lock_t *existing_lock;
585           svn_error_t *err;
586
587           svn_pool_clear(iterpool);
588
589           path = apr_hash_this_key(hi);
590           token = apr_hash_this_val(hi);
591
592           if (token && token[0])
593             continue;
594
595           if (session->cancel_func)
596             SVN_ERR(session->cancel_func(session->cancel_baton));
597
598           err = svn_ra_serf__get_lock(ra_session, &existing_lock, path,
599                                       iterpool);
600
601           if (!err && existing_lock)
602             {
603               svn_hash_sets(path_tokens, path,
604                             apr_pstrdup(scratch_pool, existing_lock->token));
605               continue;
606             }
607
608           err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err,
609                                   _("'%s' is not locked in the repository"),
610                                   path);
611
612           if (lock_func)
613             {
614               svn_error_t *err2;
615               err2 = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
616               svn_error_clear(err);
617
618               SVN_ERR(err2);
619             }
620           else
621             {
622               svn_error_clear(err);
623             }
624
625           svn_hash_sets(path_tokens, path, NULL);
626         }
627     }
628
629   /* ### Perhaps we should open more connections than just one? See update.c */
630
631   lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_tokens),
632                                  sizeof(lock_ctx_t*));
633
634   for (hi = apr_hash_first(scratch_pool, path_tokens);
635        hi;
636        hi = apr_hash_next(hi))
637     {
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;
642
643       svn_pool_clear(iterpool);
644
645       lock_pool = svn_pool_create(scratch_pool);
646       lock_ctx = apr_pcalloc(lock_pool, sizeof(*lock_ctx));
647
648       lock_ctx->pool = lock_pool;
649
650       lock_ctx->path = apr_hash_this_key(hi);
651       token = apr_hash_this_val(hi);
652
653       lock_ctx->force = force;
654       lock_ctx->token = apr_pstrcat(lock_pool, "<", token, ">", SVN_VA_NULL);
655
656       req_url = svn_path_url_add_component2(session->session_url.path, lock_ctx->path,
657                                             lock_pool);
658
659       handler = svn_ra_serf__create_handler(session, lock_pool);
660
661       handler->method = "UNLOCK";
662       handler->path = req_url;
663
664       handler->header_delegate = set_unlock_headers;
665       handler->header_delegate_baton = lock_ctx;
666
667       handler->response_handler = svn_ra_serf__expect_empty_body;
668       handler->response_baton = handler;
669
670       handler->no_fail_on_http_failure_status = TRUE;
671
672       lock_ctx->handler = handler;
673
674       APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
675
676       svn_ra_serf__request_create(handler);
677     }
678
679   SVN_ERR(run_locks(session, lock_requests, FALSE, lock_func, lock_baton,
680                     iterpool));
681
682   svn_pool_destroy(iterpool);
683
684   return SVN_NO_ERROR;
685 }