]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/lock.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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                                             _("Unlock of '%s' failed (%d %s)"),
271                                             ctx->path,
272                                             ctx->handler->sline.code,
273                                             ctx->handler->sline.reason);
274                     break;
275                   case 405:
276                     err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE,
277                                             NULL,
278                                             _("Path '%s' doesn't exist in "
279                                               "HEAD revision (%d %s)"),
280                                             ctx->path,
281                                             ctx->handler->sline.code,
282                                             ctx->handler->sline.reason);
283                     break;
284                   case 423:
285                     err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
286                                             NULL,
287                                             _("Path '%s' already locked "
288                                               "(%d %s)"),
289                                             ctx->path,
290                                             ctx->handler->sline.code,
291                                             ctx->handler->sline.reason);
292                     break;
293
294                   case 404:
295                   case 409:
296                   case 500:
297                     if (server_err)
298                       {
299                         /* Handle out of date, etc by just passing the server
300                            error */
301                         err = NULL;
302                         break;
303                       }
304
305                     /* Fall through */
306                   default:
307                     err = svn_ra_serf__unexpected_status(ctx->handler);
308                     break;
309                 }
310
311               if (server_err && err && server_err->apr_err == err->apr_err)
312                 err = svn_error_compose_create(server_err, err);
313               else
314                 err = svn_error_compose_create(err, server_err);
315
316               if (err
317                   && !SVN_ERR_IS_UNLOCK_ERROR(err)
318                   && !SVN_ERR_IS_LOCK_ERROR(err))
319                 {
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
323                      out of sync... */
324
325                   if (lock_func &&
326                       err->apr_err == SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED)
327                     {
328                       err = svn_error_compose_create(
329                                   err, lock_func(lock_baton, ctx->path, locking,
330                                                  NULL, NULL, ctx->pool));
331                     }
332
333                   return svn_error_trace(err); /* Don't go through callbacks */
334                 }
335
336               if (lock_func)
337                 {
338                   svn_lock_t *report_lock = NULL;
339
340                   if (locking && ctx->lock->token)
341                     report_lock = ctx->lock;
342
343                   cb_err = lock_func(lock_baton, ctx->path, locking,
344                                      report_lock, err, ctx->pool);
345                 }
346               svn_error_clear(err);
347
348               SVN_ERR(cb_err);
349
350               waittime_left = sess->timeout;
351               svn_sort__array_delete(lock_ctxs, i, 1);
352               i--;
353
354               svn_pool_destroy(ctx->pool);
355               continue;
356             }
357         }
358     }
359   svn_pool_destroy(iterpool);
360
361   return SVN_NO_ERROR;
362 }
363
364 /* Implements svn_ra_serf__response_handler_t */
365 static svn_error_t *
366 handle_lock(serf_request_t *request,
367             serf_bucket_t *response,
368             void *handler_baton,
369             apr_pool_t *pool)
370 {
371   lock_ctx_t *ctx = handler_baton;
372
373   if (!ctx->read_headers)
374     {
375       serf_bucket_t *headers;
376       const char *val;
377
378       headers = serf_bucket_response_get_headers(response);
379
380       val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
381       if (val)
382         {
383           ctx->lock->owner = apr_pstrdup(ctx->pool, val);
384         }
385
386       val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
387       if (val)
388         {
389           SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
390                                         ctx->pool));
391         }
392
393       ctx->read_headers = TRUE;
394     }
395
396   return ctx->inner_handler(request, response, ctx->inner_baton, pool);
397 }
398
399 /* Implements svn_ra_serf__request_body_delegate_t */
400 static svn_error_t *
401 create_lock_body(serf_bucket_t **body_bkt,
402                  void *baton,
403                  serf_bucket_alloc_t *alloc,
404                  apr_pool_t *pool /* request pool */,
405                  apr_pool_t *scratch_pool)
406 {
407   lock_ctx_t *ctx = baton;
408   serf_bucket_t *buckets;
409
410   buckets = serf_bucket_aggregate_create(alloc);
411
412   svn_ra_serf__add_xml_header_buckets(buckets, alloc);
413   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
414                                     "xmlns", "DAV:",
415                                     SVN_VA_NULL);
416
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");
420
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");
424
425   if (ctx->lock->comment)
426     {
427       svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
428                                    alloc);
429     }
430
431   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
432
433   *body_bkt = buckets;
434   return SVN_NO_ERROR;
435 }
436
437 svn_error_t *
438 svn_ra_serf__lock(svn_ra_session_t *ra_session,
439                   apr_hash_t *path_revs,
440                   const char *comment,
441                   svn_boolean_t force,
442                   svn_ra_lock_callback_t lock_func,
443                   void *lock_baton,
444                   apr_pool_t *scratch_pool)
445 {
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;
450
451   lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_revs),
452                                  sizeof(lock_ctx_t*));
453
454   /* ### Perhaps we should open more connections than just one? See update.c */
455
456   iterpool = svn_pool_create(scratch_pool);
457
458   for (hi = apr_hash_first(scratch_pool, path_revs);
459        hi;
460        hi = apr_hash_next(hi))
461     {
462       svn_ra_serf__handler_t *handler;
463       svn_ra_serf__xml_context_t *xmlctx;
464       const char *req_url;
465       lock_ctx_t *lock_ctx;
466       apr_pool_t *lock_pool;
467
468       svn_pool_clear(iterpool);
469
470       lock_pool = svn_pool_create(scratch_pool);
471       lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
472
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;
479
480       lock_ctx->force = force;
481       req_url = svn_path_url_add_component2(session->session_url.path,
482                                             lock_ctx->path, lock_pool);
483
484       xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
485                                                NULL, locks_closed, NULL,
486                                                lock_ctx,
487                                                lock_pool);
488       handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
489                                                   lock_pool);
490
491       handler->method = "LOCK";
492       handler->path = req_url;
493       handler->body_type = "text/xml";
494
495       /* Same stupid algorithm from get_best_connection() in update.c */
496       handler->conn = session->conns[session->cur_conn];
497       session->cur_conn++;
498
499       if (session->cur_conn >= session->num_conns)
500         session->cur_conn = 0;
501
502       handler->header_delegate = set_lock_headers;
503       handler->header_delegate_baton = lock_ctx;
504
505       handler->body_delegate = create_lock_body;
506       handler->body_delegate_baton = lock_ctx;
507
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;
512
513       handler->no_fail_on_http_failure_status = TRUE;
514
515       lock_ctx->handler = handler;
516
517       APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
518
519       svn_ra_serf__request_create(handler);
520     }
521
522   SVN_ERR(run_locks(session, lock_requests, TRUE, lock_func, lock_baton,
523                     iterpool));
524
525   svn_pool_destroy(iterpool);
526
527   return SVN_NO_ERROR;
528 }
529
530 static svn_error_t *
531 set_unlock_headers(serf_bucket_t *headers,
532                    void *baton,
533                    apr_pool_t *pool /* request pool */,
534                    apr_pool_t *scratch_pool)
535 {
536   lock_ctx_t *ctx = baton;
537
538   serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
539   if (ctx->force)
540     {
541       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
542                               SVN_DAV_OPTION_LOCK_BREAK);
543     }
544
545   return SVN_NO_ERROR;
546 }
547
548 svn_error_t *
549 svn_ra_serf__unlock(svn_ra_session_t *ra_session,
550                     apr_hash_t *path_tokens,
551                     svn_boolean_t force,
552                     svn_ra_lock_callback_t lock_func,
553                     void *lock_baton,
554                     apr_pool_t *scratch_pool)
555 {
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;
560
561   iterpool = svn_pool_create(scratch_pool);
562
563   /* If we are stealing locks we need the lock tokens */
564   if (force)
565     {
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 *
570        */
571
572       for (hi = apr_hash_first(scratch_pool, path_tokens);
573        hi;
574        hi = apr_hash_next(hi))
575         {
576           const char *path;
577           const char *token;
578           svn_lock_t *existing_lock;
579           svn_error_t *err;
580
581           svn_pool_clear(iterpool);
582
583           path = apr_hash_this_key(hi);
584           token = apr_hash_this_val(hi);
585
586           if (token && token[0])
587             continue;
588
589           if (session->cancel_func)
590             SVN_ERR(session->cancel_func(session->cancel_baton));
591
592           err = svn_ra_serf__get_lock(ra_session, &existing_lock, path,
593                                       iterpool);
594
595           if (!err && existing_lock)
596             {
597               svn_hash_sets(path_tokens, path,
598                             apr_pstrdup(scratch_pool, existing_lock->token));
599               continue;
600             }
601
602           err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err,
603                                   _("'%s' is not locked in the repository"),
604                                   path);
605
606           if (lock_func)
607             {
608               svn_error_t *err2;
609               err2 = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
610               svn_error_clear(err);
611
612               SVN_ERR(err2);
613             }
614           else
615             {
616               svn_error_clear(err);
617             }
618
619           svn_hash_sets(path_tokens, path, NULL);
620         }
621     }
622
623   /* ### Perhaps we should open more connections than just one? See update.c */
624
625   lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_tokens),
626                                  sizeof(lock_ctx_t*));
627
628   for (hi = apr_hash_first(scratch_pool, path_tokens);
629        hi;
630        hi = apr_hash_next(hi))
631     {
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;
636
637       svn_pool_clear(iterpool);
638
639       lock_pool = svn_pool_create(scratch_pool);
640       lock_ctx = apr_pcalloc(lock_pool, sizeof(*lock_ctx));
641
642       lock_ctx->pool = lock_pool;
643
644       lock_ctx->path = apr_hash_this_key(hi);
645       token = apr_hash_this_val(hi);
646
647       lock_ctx->force = force;
648       lock_ctx->token = apr_pstrcat(lock_pool, "<", token, ">", SVN_VA_NULL);
649
650       req_url = svn_path_url_add_component2(session->session_url.path, lock_ctx->path,
651                                             lock_pool);
652
653       handler = svn_ra_serf__create_handler(session, lock_pool);
654
655       handler->method = "UNLOCK";
656       handler->path = req_url;
657
658       handler->header_delegate = set_unlock_headers;
659       handler->header_delegate_baton = lock_ctx;
660
661       handler->response_handler = svn_ra_serf__expect_empty_body;
662       handler->response_baton = handler;
663
664       handler->no_fail_on_http_failure_status = TRUE;
665
666       lock_ctx->handler = handler;
667
668       APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
669
670       svn_ra_serf__request_create(handler);
671     }
672
673   SVN_ERR(run_locks(session, lock_requests, FALSE, lock_func, lock_baton,
674                     iterpool));
675
676   svn_pool_destroy(iterpool);
677
678   return SVN_NO_ERROR;
679 }