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
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
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 *
store_locks_callback(void * baton,const char * rel_url,svn_boolean_t do_lock,const svn_lock_t * lock,svn_error_t * ra_err,apr_pool_t * pool)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 *
condense_targets(const char ** common_parent,apr_array_header_t ** target_relpaths,const apr_array_header_t * targets,svn_boolean_t targets_are_uris,svn_boolean_t remove_redundancies,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
organize_lock_targets(const char ** common_parent_url,const char ** base_dir,apr_hash_t ** rel_targets_p,apr_hash_t ** rel_fs_paths_p,const apr_array_header_t * targets,svn_boolean_t do_lock,svn_boolean_t force,svn_wc_context_t * wc_ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
fetch_tokens(svn_ra_session_t * ra_session,apr_hash_t * path_tokens,apr_pool_t * pool)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 *
svn_client_lock(const apr_array_header_t * targets,const char * comment,svn_boolean_t steal_lock,svn_client_ctx_t * ctx,apr_pool_t * pool)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 *
svn_client_unlock(const apr_array_header_t * targets,svn_boolean_t break_lock,svn_client_ctx_t * ctx,apr_pool_t * pool)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
553