]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/locking_commands.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_client / locking_commands.c
1 /*
2  * locking_commands.c:  Implementation of lock and unlock.
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
26
27 \f
28 /*** Includes. ***/
29
30 #include "svn_client.h"
31 #include "svn_hash.h"
32 #include "client.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_path.h"
35 #include "svn_xml.h"
36 #include "svn_pools.h"
37
38 #include "svn_private_config.h"
39 #include "private/svn_client_private.h"
40 #include "private/svn_wc_private.h"
41
42 \f
43 /*** Code. ***/
44
45 /* For use with store_locks_callback, below. */
46 struct lock_baton
47 {
48   const char *base_dir_abspath;
49   apr_hash_t *urls_to_paths; /* url -> abspath */
50   const char *base_url;
51   svn_client_ctx_t *ctx;
52   apr_pool_t *pool;
53 };
54
55
56 /* This callback is called by the ra_layer for each path locked.
57  * BATON is a 'struct lock_baton *', PATH is the path being locked,
58  * and LOCK is the lock itself.
59  *
60  * If BATON->urls_to_paths is not null, then this function either
61  * stores the LOCK on REL_URL or removes any lock tokens from REL_URL
62  * (depending on whether DO_LOCK is true or false respectively), but
63  * only if RA_ERR is null, or (in the unlock case) is something other
64  * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
65  *
66  * Implements svn_ra_lock_callback_t.
67  */
68 static svn_error_t *
69 store_locks_callback(void *baton,
70                      const char *rel_url,
71                      svn_boolean_t do_lock,
72                      const svn_lock_t *lock,
73                      svn_error_t *ra_err, apr_pool_t *pool)
74 {
75   struct lock_baton *lb = baton;
76   svn_wc_notify_t *notify;
77   const char *local_abspath = lb->urls_to_paths
78                                   ? svn_hash_gets(lb->urls_to_paths, rel_url)
79                                   : NULL;
80
81   /* Create the notify struct first, so we can tweak it below. */
82   notify = svn_wc_create_notify(local_abspath ? local_abspath : rel_url,
83                                 do_lock
84                                 ? (ra_err
85                                    ? svn_wc_notify_failed_lock
86                                    : svn_wc_notify_locked)
87                                 : (ra_err
88                                    ? svn_wc_notify_failed_unlock
89                                    : svn_wc_notify_unlocked),
90                                 pool);
91   notify->lock = lock;
92   notify->err = ra_err;
93
94   if (local_abspath)
95     {
96       /* Notify a valid working copy path */
97       notify->path_prefix = lb->base_dir_abspath;
98
99       if (do_lock)
100         {
101           if (!ra_err && lock)
102             {
103               SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
104                                        lb->pool));
105               notify->lock_state = svn_wc_notify_lock_state_locked;
106             }
107           else
108             notify->lock_state = svn_wc_notify_lock_state_unchanged;
109         }
110       else /* unlocking */
111         {
112           /* Remove our wc lock token either a) if we got no error, or b) if
113              we got any error except for owner mismatch or hook failure (the
114              hook would be pre-unlock rather than post-unlock). Note that the
115              only errors that are handed to this callback will be
116              locking-related errors. */
117
118           if (!ra_err ||
119               (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH
120                           && ra_err->apr_err != SVN_ERR_REPOS_HOOK_FAILURE)))
121             {
122               SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath,
123                                           lb->pool));
124               notify->lock_state = svn_wc_notify_lock_state_unlocked;
125             }
126           else
127             notify->lock_state = svn_wc_notify_lock_state_unchanged;
128         }
129     }
130   else
131     notify->url = svn_path_url_add_component2(lb->base_url, rel_url, pool);
132
133   if (lb->ctx->notify_func2)
134     lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool);
135
136   return SVN_NO_ERROR;
137 }
138
139
140 /* This is a wrapper around svn_uri_condense_targets() and
141  * svn_dirent_condense_targets() (the choice of which is made based on
142  * the value of TARGETS_ARE_URIS) which takes care of the
143  * single-target special case.
144  *
145  * Callers are expected to check for an empty *COMMON_PARENT (which
146  * means, "there was nothing common") for themselves.
147  */
148 static svn_error_t *
149 condense_targets(const char **common_parent,
150                  apr_array_header_t **target_relpaths,
151                  const apr_array_header_t *targets,
152                  svn_boolean_t targets_are_uris,
153                  svn_boolean_t remove_redundancies,
154                  apr_pool_t *result_pool,
155                  apr_pool_t *scratch_pool)
156 {
157   if (targets_are_uris)
158     {
159       SVN_ERR(svn_uri_condense_targets(common_parent, target_relpaths,
160                                        targets, remove_redundancies,
161                                        result_pool, scratch_pool));
162     }
163   else
164     {
165       SVN_ERR(svn_dirent_condense_targets(common_parent, target_relpaths,
166                                           targets, remove_redundancies,
167                                           result_pool, scratch_pool));
168     }
169
170   /* svn_*_condense_targets leaves *TARGET_RELPATHS empty if TARGETS only
171      had 1 member, so we special case that. */
172   if (apr_is_empty_array(*target_relpaths))
173     {
174       const char *base_name;
175
176       if (targets_are_uris)
177         {
178           svn_uri_split(common_parent, &base_name,
179                         *common_parent, result_pool);
180         }
181       else
182         {
183           svn_dirent_split(common_parent, &base_name,
184                            *common_parent, result_pool);
185         }
186       APR_ARRAY_PUSH(*target_relpaths, const char *) = base_name;
187     }
188
189   return SVN_NO_ERROR;
190 }
191
192 /* Lock info. Used in organize_lock_targets.
193    ### Maybe return this instead of the ugly hashes? */
194 struct wc_lock_item_t
195 {
196   svn_revnum_t revision;
197   const char *lock_token;
198   const char *url;
199 };
200
201 /*
202  * Sets LOCK_PATHS to an array of working copy paths that this function
203  * has obtained lock on. The caller is responsible to release the locks
204  * EVEN WHEN THIS FUNCTION RETURNS AN ERROR.
205  *
206  * Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS.
207  * If TARGETS are local paths, then the entry for each path is examined
208  * and *COMMON_PARENT is set to the common parent URL for all the
209  * targets (as opposed to the common local path).
210  *
211  * If there is no common parent, either because the targets are a
212  * mixture of URLs and local paths, or because they simply do not
213  * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE.
214  *
215  * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them.
216  * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise.
217  *
218  * Each key stored in *REL_TARGETS_P is a path relative to
219  * *COMMON_PARENT.  If TARGETS are local paths, then: if DO_LOCK is
220  * true, the value is a pointer to the corresponding base_revision
221  * (allocated in POOL) for the path, else the value is the lock token
222  * (or "" if no token found in the wc).
223  *
224  * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL.
225  * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to
226  * COMMON_PARENT) mapped to the absolute path for TARGET.
227  *
228  * If *COMMON_PARENT is a URL, then the values are a pointer to
229  * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "".
230  *
231  * TARGETS may not be empty.
232  */
233 static svn_error_t *
234 organize_lock_targets(apr_array_header_t **lock_paths,
235                       const char **common_parent_url,
236                       const char **base_dir_abspath,
237                       apr_hash_t **rel_targets_p,
238                       apr_hash_t **rel_fs_paths_p,
239                       const apr_array_header_t *targets,
240                       svn_boolean_t do_lock,
241                       svn_boolean_t force,
242                       svn_wc_context_t *wc_ctx,
243                       apr_pool_t *result_pool,
244                       apr_pool_t *scratch_pool)
245 {
246   const char *common_url = NULL;
247   apr_hash_t *rel_targets_ret = apr_hash_make(result_pool);
248   apr_hash_t *rel_fs_paths = NULL;
249   apr_hash_t *wc_info = apr_hash_make(scratch_pool);
250   svn_boolean_t url_mode;
251
252   *lock_paths = NULL;
253
254   SVN_ERR_ASSERT(targets->nelts);
255   SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
256
257   url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
258
259   if (url_mode)
260     {
261       apr_array_header_t *rel_targets;
262       int i;
263       svn_revnum_t *invalid_revnum =
264         apr_palloc(result_pool, sizeof(*invalid_revnum));
265
266       *invalid_revnum = SVN_INVALID_REVNUM;
267
268       /* Get the common parent URL and a bunch of relpaths, one per target. */
269       SVN_ERR(condense_targets(&common_url, &rel_targets, targets,
270                                TRUE, TRUE, result_pool, scratch_pool));
271       if (! (common_url && *common_url))
272         return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
273                                 _("No common parent found, unable to operate "
274                                   "on disjoint arguments"));
275
276       /* Create mapping of the target relpaths to either
277          SVN_INVALID_REVNUM (if our caller is locking) or to an empty
278          lock token string (if the caller is unlocking). */
279       for (i = 0; i < rel_targets->nelts; i++)
280         {
281           svn_hash_sets(rel_targets_ret,
282                         APR_ARRAY_IDX(rel_targets, i, const char *),
283                         do_lock
284                         ? (const void *)invalid_revnum
285                         : (const void *)"");
286         }
287     }
288   else
289     {
290       apr_array_header_t *rel_urls, *target_urls;
291       apr_hash_t *wcroot_target = apr_hash_make(scratch_pool);
292       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
293       apr_hash_index_t *hi;
294       int i;
295
296       *lock_paths = apr_array_make(result_pool, 1, sizeof(const char *));
297
298       for (i = 0; i < targets->nelts; i++)
299         {
300           const char *target_abspath;
301           const char *wcroot_abspath;
302           apr_array_header_t *wc_targets;
303
304           svn_pool_clear(iterpool);
305
306           SVN_ERR(svn_dirent_get_absolute(&target_abspath,
307                                           APR_ARRAY_IDX(targets, i, const char*),
308                                           result_pool));
309
310           SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
311                                      iterpool, iterpool));
312
313           wc_targets = svn_hash_gets(wcroot_target, wcroot_abspath);
314
315           if (!wc_targets)
316             {
317               wc_targets = apr_array_make(scratch_pool, 1, sizeof(const char*));
318               svn_hash_sets(wcroot_target, apr_pstrdup(scratch_pool, wcroot_abspath),
319                             wc_targets);
320             }
321
322           APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
323         }
324
325       for (hi = apr_hash_first(scratch_pool, wcroot_target);
326            hi;
327            hi = apr_hash_next(hi))
328         {
329           const char *lock_abspath;
330           apr_array_header_t *paths = apr_hash_this_val(hi);
331
332           /* Use parent dir of a single file target */
333           if (paths->nelts == 1)
334             lock_abspath = svn_dirent_dirname(
335                                 APR_ARRAY_IDX(paths, 0, const char *),
336                                 result_pool);
337           else
338             SVN_ERR(svn_dirent_condense_targets(&lock_abspath, NULL, paths,
339                                                 FALSE, result_pool,
340                                                 scratch_pool));
341
342           SVN_ERR(svn_wc__acquire_write_lock(&lock_abspath,
343                                              wc_ctx, lock_abspath, FALSE,
344                                              result_pool, scratch_pool));
345
346           APR_ARRAY_PUSH(*lock_paths, const char *) = lock_abspath;
347         }
348
349       /* Get the URL for each target (which also serves to verify that
350          the dirent targets are sane).  */
351       target_urls = apr_array_make(scratch_pool, targets->nelts,
352                                    sizeof(const char *));
353       for (hi = apr_hash_first(scratch_pool, wcroot_target);
354            hi;
355            hi = apr_hash_next(hi))
356         {
357           apr_array_header_t *wc_targets = apr_hash_this_val(hi);
358
359           for (i = 0; i < wc_targets->nelts; i++)
360             {
361               const char *repos_relpath;
362               const char *repos_root_url;
363               struct wc_lock_item_t *wli;
364               const char *local_abspath;
365               svn_node_kind_t kind;
366
367               svn_pool_clear(iterpool);
368
369               local_abspath = APR_ARRAY_IDX(wc_targets, i, const char *);
370               wli = apr_pcalloc(scratch_pool, sizeof(*wli));
371
372               SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision,
373                                             &repos_relpath,
374                                             &repos_root_url, NULL,
375                                             &wli->lock_token,
376                                             wc_ctx, local_abspath,
377                                             FALSE /* ignore_enoent */,
378                                             result_pool, iterpool));
379
380               if (kind != svn_node_file)
381                 return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL,
382                                          _("The node '%s' is not a file"),
383                                          svn_dirent_local_style(local_abspath,
384                                                                 iterpool));
385
386               wli->url = svn_path_url_add_component2(repos_root_url,
387                                                      repos_relpath,
388                                                      scratch_pool);
389               svn_hash_sets(wc_info, local_abspath, wli);
390
391               APR_ARRAY_PUSH(target_urls, const char *) = wli->url;
392             }
393         }
394
395       /* Now that we have a bunch of URLs for our dirent targets,
396          condense those into a single common parent URL and a bunch of
397          paths relative to that. */
398       SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls,
399                                TRUE, FALSE, result_pool, scratch_pool));
400       if (! (common_url && *common_url))
401         return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
402                                 _("Unable to lock/unlock across multiple "
403                                   "repositories"));
404
405       /* Now we need to create our mapping. */
406       rel_fs_paths = apr_hash_make(result_pool);
407
408       for (hi = apr_hash_first(scratch_pool, wc_info);
409            hi;
410            hi = apr_hash_next(hi))
411         {
412           const char *local_abspath = apr_hash_this_key(hi);
413           struct wc_lock_item_t *wli = apr_hash_this_val(hi);
414           const char *rel_url;
415
416           svn_pool_clear(iterpool);
417
418           rel_url = svn_uri_skip_ancestor(common_url, wli->url, result_pool);
419
420           svn_hash_sets(rel_fs_paths, rel_url,
421                         apr_pstrdup(result_pool, local_abspath));
422
423           if (do_lock) /* Lock. */
424             {
425               svn_revnum_t *revnum;
426               revnum = apr_palloc(result_pool, sizeof(* revnum));
427
428               *revnum = wli->revision;
429
430               svn_hash_sets(rel_targets_ret, rel_url, revnum);
431             }
432           else /* Unlock. */
433             {
434               const char *lock_token;
435
436               /* If not forcing the unlock, get the lock token. */
437               if (! force)
438                 {
439                   if (! wli->lock_token)
440                     return svn_error_createf(
441                                SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
442                                _("'%s' is not locked in this working copy"),
443                                svn_dirent_local_style(local_abspath,
444                                                       scratch_pool));
445
446                   lock_token = wli->lock_token
447                                 ? apr_pstrdup(result_pool, wli->lock_token)
448                                 : NULL;
449                 }
450               else
451                 lock_token = NULL;
452
453               /* If breaking a lock, we shouldn't pass any lock token. */
454               svn_hash_sets(rel_targets_ret, rel_url,
455                             lock_token ? lock_token : "");
456             }
457         }
458
459       svn_pool_destroy(iterpool);
460     }
461
462   /* Set our return variables. */
463   *common_parent_url = common_url;
464   if (*lock_paths && (*lock_paths)->nelts == 1)
465     *base_dir_abspath = APR_ARRAY_IDX(*lock_paths, 0, const char*);
466   else
467     *base_dir_abspath = NULL;
468   *rel_targets_p = rel_targets_ret;
469   *rel_fs_paths_p = rel_fs_paths;
470
471   return SVN_NO_ERROR;
472 }
473
474 /* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
475    setting the values to the fetched tokens, allocated in pool. */
476 static svn_error_t *
477 fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
478              apr_pool_t *pool)
479 {
480   apr_hash_index_t *hi;
481   apr_pool_t *iterpool = svn_pool_create(pool);
482
483   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
484     {
485       const char *path = apr_hash_this_key(hi);
486       svn_lock_t *lock;
487
488       svn_pool_clear(iterpool);
489
490       SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
491
492       if (! lock)
493         return svn_error_createf
494           (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
495            _("'%s' is not locked"), path);
496
497       svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token));
498     }
499
500   svn_pool_destroy(iterpool);
501   return SVN_NO_ERROR;
502 }
503
504
505 svn_error_t *
506 svn_client_lock(const apr_array_header_t *targets,
507                 const char *comment,
508                 svn_boolean_t steal_lock,
509                 svn_client_ctx_t *ctx,
510                 apr_pool_t *pool)
511 {
512   const char *base_dir_abspath = NULL;
513   const char *common_parent_url;
514   svn_ra_session_t *ra_session;
515   apr_hash_t *path_revs, *urls_to_paths;
516   struct lock_baton cb;
517   apr_array_header_t *lock_abspaths;
518   svn_error_t *err;
519
520   if (apr_is_empty_array(targets))
521     return SVN_NO_ERROR;
522
523   /* Enforce that the comment be xml-escapable. */
524   if (comment)
525     {
526       if (! svn_xml_is_xml_safe(comment, strlen(comment)))
527         return svn_error_create
528           (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
529            _("Lock comment contains illegal characters"));
530     }
531
532   err = organize_lock_targets(&lock_abspaths, &common_parent_url,
533                               &base_dir_abspath, &path_revs,
534                               &urls_to_paths,
535                               targets, TRUE, steal_lock,
536                               ctx->wc_ctx, pool, pool);
537
538   if (err)
539     goto release_locks;
540
541   /* Open an RA session to the common parent URL of TARGETS. */
542   err = svn_client_open_ra_session2(&ra_session, common_parent_url,
543                                     base_dir_abspath, ctx, pool, pool);
544
545   if (err)
546     goto release_locks;
547
548   cb.base_dir_abspath = base_dir_abspath;
549   cb.base_url = common_parent_url;
550   cb.urls_to_paths = urls_to_paths;
551   cb.ctx = ctx;
552   cb.pool = pool;
553
554   /* Lock the paths. */
555   err = svn_ra_lock(ra_session, path_revs, comment,
556                     steal_lock, store_locks_callback, &cb, pool);
557
558 release_locks:
559   if (lock_abspaths)
560     {
561       int i;
562
563       for (i = 0; i < lock_abspaths->nelts; i++)
564         {
565           err = svn_error_compose_create(
566                   err,
567                   svn_wc__release_write_lock(ctx->wc_ctx,
568                                              APR_ARRAY_IDX(lock_abspaths, i,
569                                                            const char *),
570                                              pool));
571         }
572     }
573
574   return svn_error_trace(err);
575 }
576
577 svn_error_t *
578 svn_client_unlock(const apr_array_header_t *targets,
579                   svn_boolean_t break_lock,
580                   svn_client_ctx_t *ctx,
581                   apr_pool_t *pool)
582 {
583   const char *base_dir_abspath = NULL;
584   const char *common_parent_url;
585   svn_ra_session_t *ra_session;
586   apr_hash_t *path_tokens, *urls_to_paths;
587   apr_array_header_t *lock_abspaths;
588   struct lock_baton cb;
589   svn_error_t *err;
590
591   if (apr_is_empty_array(targets))
592     return SVN_NO_ERROR;
593
594   err = organize_lock_targets(&lock_abspaths, &common_parent_url,
595                               &base_dir_abspath, &path_tokens, &urls_to_paths,
596                               targets, FALSE, break_lock,
597                               ctx->wc_ctx, pool, pool);
598
599   if (err)
600     goto release_locks;
601
602   /* Open an RA session to the common parent URL of TARGETS. */
603   err = svn_client_open_ra_session2(&ra_session, common_parent_url,
604                                     base_dir_abspath, ctx, pool, pool);
605
606   if (err)
607     goto release_locks;
608
609   /* If break_lock is not set, lock tokens are required by the server.
610      If the targets were all URLs, ensure that we provide lock tokens,
611      so the repository will only check that the user owns the
612      locks. */
613   if (! lock_abspaths && !break_lock)
614     {
615       err = fetch_tokens(ra_session, path_tokens, pool);
616
617       if (err)
618         goto release_locks;
619     }
620
621   cb.base_dir_abspath = base_dir_abspath;
622   cb.base_url = common_parent_url;
623   cb.urls_to_paths = urls_to_paths;
624   cb.ctx = ctx;
625   cb.pool = pool;
626
627   /* Unlock the paths. */
628   err = svn_ra_unlock(ra_session, path_tokens, break_lock,
629                       store_locks_callback, &cb, pool);
630
631 release_locks:
632   if (lock_abspaths)
633     {
634       int i;
635
636       for (i = 0; i < lock_abspaths->nelts; i++)
637         {
638           err = svn_error_compose_create(
639                   err,
640                   svn_wc__release_write_lock(ctx->wc_ctx,
641                                              APR_ARRAY_IDX(lock_abspaths, i,
642                                                            const char *),
643                                              pool));
644         }
645     }
646
647   return svn_error_trace(err);
648 }
649