]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/locking_commands.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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;
50   svn_client_ctx_t *ctx;
51   apr_pool_t *pool;
52 };
53
54
55 /* This callback is called by the ra_layer for each path locked.
56  * BATON is a 'struct lock_baton *', PATH is the path being locked,
57  * and LOCK is the lock itself.
58  *
59  * If BATON->base_dir_abspath is not null, then this function either
60  * stores the LOCK on REL_URL or removes any lock tokens from REL_URL
61  * (depending on whether DO_LOCK is true or false respectively), but
62  * only if RA_ERR is null, or (in the unlock case) is something other
63  * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
64  *
65  * Implements svn_ra_lock_callback_t.
66  */
67 static svn_error_t *
68 store_locks_callback(void *baton,
69                      const char *rel_url,
70                      svn_boolean_t do_lock,
71                      const svn_lock_t *lock,
72                      svn_error_t *ra_err, apr_pool_t *pool)
73 {
74   struct lock_baton *lb = baton;
75   svn_wc_notify_t *notify;
76
77   /* Create the notify struct first, so we can tweak it below. */
78   notify = svn_wc_create_notify(rel_url,
79                                 do_lock
80                                 ? (ra_err
81                                    ? svn_wc_notify_failed_lock
82                                    : svn_wc_notify_locked)
83                                 : (ra_err
84                                    ? svn_wc_notify_failed_unlock
85                                    : svn_wc_notify_unlocked),
86                                 pool);
87   notify->lock = lock;
88   notify->err = ra_err;
89
90   if (lb->base_dir_abspath)
91     {
92       char *path = svn_hash_gets(lb->urls_to_paths, rel_url);
93       const char *local_abspath;
94
95       local_abspath = svn_dirent_join(lb->base_dir_abspath, path, pool);
96
97       /* Notify a valid working copy path */
98       notify->path = local_abspath;
99       notify->path_prefix = lb->base_dir_abspath;
100
101       if (do_lock)
102         {
103           if (!ra_err)
104             {
105               SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
106                                        lb->pool));
107               notify->lock_state = svn_wc_notify_lock_state_locked;
108             }
109           else
110             notify->lock_state = svn_wc_notify_lock_state_unchanged;
111         }
112       else /* unlocking */
113         {
114           /* Remove our wc lock token either a) if we got no error, or b) if
115              we got any error except for owner mismatch.  Note that the only
116              errors that are handed to this callback will be locking-related
117              errors. */
118
119           if (!ra_err ||
120               (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH)))
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 = rel_url; /* Notify that path is actually a url  */
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 };
199
200 /* Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS.
201  * If TARGETS are local paths, then the entry for each path is examined
202  * and *COMMON_PARENT is set to the common parent URL for all the
203  * targets (as opposed to the common local path).
204  *
205  * If there is no common parent, either because the targets are a
206  * mixture of URLs and local paths, or because they simply do not
207  * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE.
208  *
209  * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them.
210  * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise.
211  *
212  * Each key stored in *REL_TARGETS_P is a path relative to
213  * *COMMON_PARENT.  If TARGETS are local paths, then: if DO_LOCK is
214  * true, the value is a pointer to the corresponding base_revision
215  * (allocated in POOL) for the path, else the value is the lock token
216  * (or "" if no token found in the wc).
217  *
218  * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL.
219  * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to
220  * COMMON_PARENT) mapped to the target path for TARGET (relative to
221  * the common parent WC path). working copy targets that they "belong" to.
222  *
223  * If *COMMON_PARENT is a URL, then the values are a pointer to
224  * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "".
225  *
226  * TARGETS may not be empty.
227  */
228 static svn_error_t *
229 organize_lock_targets(const char **common_parent_url,
230                       const char **base_dir,
231                       apr_hash_t **rel_targets_p,
232                       apr_hash_t **rel_fs_paths_p,
233                       const apr_array_header_t *targets,
234                       svn_boolean_t do_lock,
235                       svn_boolean_t force,
236                       svn_wc_context_t *wc_ctx,
237                       apr_pool_t *result_pool,
238                       apr_pool_t *scratch_pool)
239 {
240   const char *common_url = NULL;
241   const char *common_dirent = NULL;
242   apr_hash_t *rel_targets_ret = apr_hash_make(result_pool);
243   apr_hash_t *rel_fs_paths = NULL;
244   apr_array_header_t *rel_targets;
245   apr_hash_t *wc_info = apr_hash_make(scratch_pool);
246   svn_boolean_t url_mode;
247   int i;
248
249   SVN_ERR_ASSERT(targets->nelts);
250   SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
251
252   url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
253
254   if (url_mode)
255     {
256       svn_revnum_t *invalid_revnum =
257         apr_palloc(result_pool, sizeof(*invalid_revnum));
258
259       *invalid_revnum = SVN_INVALID_REVNUM;
260
261       /* Get the common parent URL and a bunch of relpaths, one per target. */
262       SVN_ERR(condense_targets(&common_url, &rel_targets, targets,
263                                TRUE, TRUE, result_pool, scratch_pool));
264       if (! (common_url && *common_url))
265         return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
266                                 _("No common parent found, unable to operate "
267                                   "on disjoint arguments"));
268
269       /* Create mapping of the target relpaths to either
270          SVN_INVALID_REVNUM (if our caller is locking) or to an empty
271          lock token string (if the caller is unlocking). */
272       for (i = 0; i < rel_targets->nelts; i++)
273         {
274           svn_hash_sets(rel_targets_ret,
275                         APR_ARRAY_IDX(rel_targets, i, const char *),
276                         do_lock
277                         ? (const void *)invalid_revnum
278                         : (const void *)"");
279         }
280     }
281   else
282     {
283       apr_array_header_t *rel_urls, *target_urls;
284       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
285
286       /* Get the common parent dirent and a bunch of relpaths, one per
287          target. */
288       SVN_ERR(condense_targets(&common_dirent, &rel_targets, targets,
289                                FALSE, TRUE, result_pool, scratch_pool));
290       if (! (common_dirent && *common_dirent))
291         return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
292                                 _("No common parent found, unable to operate "
293                                   "on disjoint arguments"));
294
295       /* Get the URL for each target (which also serves to verify that
296          the dirent targets are sane).  */
297       target_urls = apr_array_make(scratch_pool, rel_targets->nelts,
298                                    sizeof(const char *));
299       for (i = 0; i < rel_targets->nelts; i++)
300         {
301           const char *rel_target;
302           const char *repos_relpath;
303           const char *repos_root_url;
304           const char *target_url;
305           struct wc_lock_item_t *wli;
306           const char *local_abspath;
307           svn_node_kind_t kind;
308
309           svn_pool_clear(iterpool);
310
311           rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
312           local_abspath = svn_dirent_join(common_dirent, rel_target, scratch_pool);
313           wli = apr_pcalloc(scratch_pool, sizeof(*wli));
314
315           SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision, &repos_relpath,
316                                         &repos_root_url, NULL,
317                                         &wli->lock_token,
318                                         wc_ctx, local_abspath,
319                                         FALSE /* ignore_enoent */,
320                                         FALSE /* show_hidden */,
321                                         result_pool, iterpool));
322
323           if (kind != svn_node_file)
324             return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL,
325                                      _("The node '%s' is not a file"),
326                                      svn_dirent_local_style(local_abspath,
327                                                             iterpool));
328
329           svn_hash_sets(wc_info, local_abspath, wli);
330
331           target_url = svn_path_url_add_component2(repos_root_url,
332                                                    repos_relpath,
333                                                    scratch_pool);
334
335           APR_ARRAY_PUSH(target_urls, const char *) = target_url;
336         }
337
338       /* Now that we have a bunch of URLs for our dirent targets,
339          condense those into a single common parent URL and a bunch of
340          paths relative to that. */
341       SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls,
342                                TRUE, FALSE, result_pool, scratch_pool));
343       if (! (common_url && *common_url))
344         return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
345                                 _("Unable to lock/unlock across multiple "
346                                   "repositories"));
347
348       /* Now we need to create a couple of different hash mappings. */
349       rel_fs_paths = apr_hash_make(result_pool);
350       for (i = 0; i < rel_targets->nelts; i++)
351         {
352           const char *rel_target, *rel_url;
353           const char *local_abspath;
354
355           svn_pool_clear(iterpool);
356
357           /* First, we need to map our REL_URL (which is relative to
358              COMMON_URL) to our REL_TARGET (which is relative to
359              COMMON_DIRENT). */
360           rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
361           rel_url = APR_ARRAY_IDX(rel_urls, i, const char *);
362           svn_hash_sets(rel_fs_paths, rel_url,
363                         apr_pstrdup(result_pool, rel_target));
364
365           /* Then, we map our REL_URL (again) to either the base
366              revision of the dirent target with which it is associated
367              (if our caller is locking) or to a (possible empty) lock
368              token string (if the caller is unlocking). */
369           local_abspath = svn_dirent_join(common_dirent, rel_target, iterpool);
370
371           if (do_lock) /* Lock. */
372             {
373               svn_revnum_t *revnum;
374               struct wc_lock_item_t *wli;
375               revnum = apr_palloc(result_pool, sizeof(* revnum));
376
377               wli = svn_hash_gets(wc_info, local_abspath);
378
379               SVN_ERR_ASSERT(wli != NULL);
380
381               *revnum = wli->revision;
382
383               svn_hash_sets(rel_targets_ret, rel_url, revnum);
384             }
385           else /* Unlock. */
386             {
387               const char *lock_token;
388               struct wc_lock_item_t *wli;
389
390               /* If not forcing the unlock, get the lock token. */
391               if (! force)
392                 {
393                   wli = svn_hash_gets(wc_info, local_abspath);
394
395                   SVN_ERR_ASSERT(wli != NULL);
396
397                   if (! wli->lock_token)
398                     return svn_error_createf(
399                                SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
400                                _("'%s' is not locked in this working copy"),
401                                svn_dirent_local_style(local_abspath,
402                                                       scratch_pool));
403
404                   lock_token = wli->lock_token
405                                 ? apr_pstrdup(result_pool, wli->lock_token)
406                                 : NULL;
407                 }
408               else
409                 lock_token = NULL;
410
411               /* If breaking a lock, we shouldn't pass any lock token. */
412               svn_hash_sets(rel_targets_ret, rel_url,
413                             lock_token ? lock_token : "");
414             }
415         }
416
417       svn_pool_destroy(iterpool);
418     }
419
420   /* Set our return variables. */
421   *common_parent_url = common_url;
422   *base_dir = common_dirent;
423   *rel_targets_p = rel_targets_ret;
424   *rel_fs_paths_p = rel_fs_paths;
425
426   return SVN_NO_ERROR;
427 }
428
429 /* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
430    setting the values to the fetched tokens, allocated in pool. */
431 static svn_error_t *
432 fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
433              apr_pool_t *pool)
434 {
435   apr_hash_index_t *hi;
436   apr_pool_t *iterpool = svn_pool_create(pool);
437
438   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
439     {
440       const char *path = svn__apr_hash_index_key(hi);
441       svn_lock_t *lock;
442
443       svn_pool_clear(iterpool);
444
445       SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
446
447       if (! lock)
448         return svn_error_createf
449           (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
450            _("'%s' is not locked"), path);
451
452       svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token));
453     }
454
455   svn_pool_destroy(iterpool);
456   return SVN_NO_ERROR;
457 }
458
459
460 svn_error_t *
461 svn_client_lock(const apr_array_header_t *targets,
462                 const char *comment,
463                 svn_boolean_t steal_lock,
464                 svn_client_ctx_t *ctx,
465                 apr_pool_t *pool)
466 {
467   const char *base_dir;
468   const char *base_dir_abspath = NULL;
469   const char *common_parent_url;
470   svn_ra_session_t *ra_session;
471   apr_hash_t *path_revs, *urls_to_paths;
472   struct lock_baton cb;
473
474   if (apr_is_empty_array(targets))
475     return SVN_NO_ERROR;
476
477   /* Enforce that the comment be xml-escapable. */
478   if (comment)
479     {
480       if (! svn_xml_is_xml_safe(comment, strlen(comment)))
481         return svn_error_create
482           (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
483            _("Lock comment contains illegal characters"));
484     }
485
486   SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_revs,
487                                 &urls_to_paths, targets, TRUE, steal_lock,
488                                 ctx->wc_ctx, pool, pool));
489
490   /* Open an RA session to the common parent of TARGETS. */
491   if (base_dir)
492     SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
493   SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url,
494                                       base_dir_abspath, ctx, pool, pool));
495
496   cb.base_dir_abspath = base_dir_abspath;
497   cb.urls_to_paths = urls_to_paths;
498   cb.ctx = ctx;
499   cb.pool = pool;
500
501   /* Lock the paths. */
502   SVN_ERR(svn_ra_lock(ra_session, path_revs, comment,
503                       steal_lock, store_locks_callback, &cb, pool));
504
505   return SVN_NO_ERROR;
506 }
507
508 svn_error_t *
509 svn_client_unlock(const apr_array_header_t *targets,
510                   svn_boolean_t break_lock,
511                   svn_client_ctx_t *ctx,
512                   apr_pool_t *pool)
513 {
514   const char *base_dir;
515   const char *base_dir_abspath = NULL;
516   const char *common_parent_url;
517   svn_ra_session_t *ra_session;
518   apr_hash_t *path_tokens, *urls_to_paths;
519   struct lock_baton cb;
520
521   if (apr_is_empty_array(targets))
522     return SVN_NO_ERROR;
523
524   SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_tokens,
525                                 &urls_to_paths, targets, FALSE, break_lock,
526                                 ctx->wc_ctx, pool, pool));
527
528   /* Open an RA session. */
529   if (base_dir)
530     SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
531   SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url,
532                                       base_dir_abspath, ctx, pool, pool));
533
534   /* If break_lock is not set, lock tokens are required by the server.
535      If the targets were all URLs, ensure that we provide lock tokens,
536      so the repository will only check that the user owns the
537      locks. */
538   if (! base_dir && !break_lock)
539     SVN_ERR(fetch_tokens(ra_session, path_tokens, pool));
540
541   cb.base_dir_abspath = base_dir_abspath;
542   cb.urls_to_paths = urls_to_paths;
543   cb.ctx = ctx;
544   cb.pool = pool;
545
546   /* Unlock the paths. */
547   SVN_ERR(svn_ra_unlock(ra_session, path_tokens, break_lock,
548                         store_locks_callback, &cb, pool));
549
550   return SVN_NO_ERROR;
551 }
552