1 /* lock.c : functions for manipulating filesystem locks.
2 *
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 * ====================================================================
21 */
22
23 #include "svn_pools.h"
24 #include "svn_error.h"
25 #include "svn_dirent_uri.h"
26 #include "svn_path.h"
27 #include "svn_fs.h"
28 #include "svn_hash.h"
29 #include "svn_time.h"
30 #include "svn_utf.h"
31
32 #include <apr_uuid.h>
33 #include <apr_file_io.h>
34 #include <apr_file_info.h>
35
36 #include "lock.h"
37 #include "tree.h"
38 #include "fs_fs.h"
39 #include "util.h"
40 #include "../libsvn_fs/fs-loader.h"
41
42 #include "private/svn_fs_util.h"
43 #include "private/svn_fspath.h"
44 #include "private/svn_sorts_private.h"
45 #include "svn_private_config.h"
46
47 /* Names of hash keys used to store a lock for writing to disk. */
48 #define PATH_KEY "path"
49 #define TOKEN_KEY "token"
50 #define OWNER_KEY "owner"
51 #define CREATION_DATE_KEY "creation_date"
52 #define EXPIRATION_DATE_KEY "expiration_date"
53 #define COMMENT_KEY "comment"
54 #define IS_DAV_COMMENT_KEY "is_dav_comment"
55 #define CHILDREN_KEY "children"
56
57 /* Number of characters from the head of a digest file name used to
58 calculate a subdirectory in which to drop that file. */
59 #define DIGEST_SUBDIR_LEN 3
60
61
62
63 /*** Generic helper functions. ***/
64
65 /* Set *DIGEST to the MD5 hash of STR. */
66 static svn_error_t *
make_digest(const char ** digest,const char * str,apr_pool_t * pool)67 make_digest(const char **digest,
68 const char *str,
69 apr_pool_t *pool)
70 {
71 svn_checksum_t *checksum;
72
73 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
74
75 *digest = svn_checksum_to_cstring_display(checksum, pool);
76 return SVN_NO_ERROR;
77 }
78
79
80 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
81 if unknown) to an svn_string_t-ized version of VALUE (whose size is
82 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
83 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
84 is NULL, this function will do nothing. */
85 static void
hash_store(apr_hash_t * hash,const char * key,apr_ssize_t key_len,const char * value,apr_ssize_t value_len,apr_pool_t * pool)86 hash_store(apr_hash_t *hash,
87 const char *key,
88 apr_ssize_t key_len,
89 const char *value,
90 apr_ssize_t value_len,
91 apr_pool_t *pool)
92 {
93 if (! (key && value))
94 return;
95 if (value_len == APR_HASH_KEY_STRING)
96 value_len = strlen(value);
97 apr_hash_set(hash, key, key_len,
98 svn_string_ncreate(value, value_len, pool));
99 }
100
101
102 /* Fetch the value of KEY from HASH, returning only the cstring data
103 of that value (if it exists). */
104 static const char *
hash_fetch(apr_hash_t * hash,const char * key)105 hash_fetch(apr_hash_t *hash,
106 const char *key)
107 {
108 svn_string_t *str = svn_hash_gets(hash, key);
109 return str ? str->data : NULL;
110 }
111
112
113 /* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */
114 static svn_error_t *
err_corrupt_lockfile(const char * fs_path,const char * path)115 err_corrupt_lockfile(const char *fs_path, const char *path)
116 {
117 return
118 svn_error_createf(
119 SVN_ERR_FS_CORRUPT, 0,
120 _("Corrupt lockfile for path '%s' in filesystem '%s'"),
121 path, fs_path);
122 }
123
124
125 /*** Digest file handling functions. ***/
126
127 /* Return the path of the lock/entries file for which DIGEST is the
128 hashed repository relative path. */
129 static const char *
digest_path_from_digest(const char * fs_path,const char * digest,apr_pool_t * pool)130 digest_path_from_digest(const char *fs_path,
131 const char *digest,
132 apr_pool_t *pool)
133 {
134 return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
135 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
136 digest, SVN_VA_NULL);
137 }
138
139
140 /* Set *DIGEST_PATH to the path to the lock/entries digest file associate
141 with PATH, where PATH is the path to the lock file or lock entries file
142 in FS. */
143 static svn_error_t *
digest_path_from_path(const char ** digest_path,const char * fs_path,const char * path,apr_pool_t * pool)144 digest_path_from_path(const char **digest_path,
145 const char *fs_path,
146 const char *path,
147 apr_pool_t *pool)
148 {
149 const char *digest;
150 SVN_ERR(make_digest(&digest, path, pool));
151 *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
152 apr_pstrmemdup(pool, digest,
153 DIGEST_SUBDIR_LEN),
154 digest, SVN_VA_NULL);
155 return SVN_NO_ERROR;
156 }
157
158
159 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
160 empty, if the versioned path in FS represented by DIGEST_PATH has
161 no children) and LOCK (which may be NULL if that versioned path is
162 lock itself locked). Set the permissions of DIGEST_PATH to those of
163 PERMS_REFERENCE. Use POOL for all allocations.
164 */
165 static svn_error_t *
write_digest_file(apr_hash_t * children,svn_lock_t * lock,const char * fs_path,const char * digest_path,const char * perms_reference,apr_pool_t * pool)166 write_digest_file(apr_hash_t *children,
167 svn_lock_t *lock,
168 const char *fs_path,
169 const char *digest_path,
170 const char *perms_reference,
171 apr_pool_t *pool)
172 {
173 svn_error_t *err = SVN_NO_ERROR;
174 svn_stream_t *stream;
175 apr_hash_index_t *hi;
176 apr_hash_t *hash = apr_hash_make(pool);
177 const char *tmp_path;
178
179 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
180 pool), fs_path, pool));
181 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
182 fs_path, pool));
183
184 if (lock)
185 {
186 const char *creation_date = NULL, *expiration_date = NULL;
187 if (lock->creation_date)
188 creation_date = svn_time_to_cstring(lock->creation_date, pool);
189 if (lock->expiration_date)
190 expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
191 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
192 lock->path, APR_HASH_KEY_STRING, pool);
193 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
194 lock->token, APR_HASH_KEY_STRING, pool);
195 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
196 lock->owner, APR_HASH_KEY_STRING, pool);
197 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
198 lock->comment, APR_HASH_KEY_STRING, pool);
199 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
200 lock->is_dav_comment ? "1" : "0", 1, pool);
201 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
202 creation_date, APR_HASH_KEY_STRING, pool);
203 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
204 expiration_date, APR_HASH_KEY_STRING, pool);
205 }
206 if (apr_hash_count(children))
207 {
208 svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool);
209 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
210 {
211 svn_stringbuf_appendbytes(children_list,
212 apr_hash_this_key(hi),
213 apr_hash_this_key_len(hi));
214 svn_stringbuf_appendbyte(children_list, '\n');
215 }
216 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
217 children_list->data, children_list->len, pool);
218 }
219
220 SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
221 svn_dirent_dirname(digest_path, pool),
222 svn_io_file_del_none, pool, pool));
223 if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
224 {
225 err = svn_error_compose_create(err, svn_stream_close(stream));
226 return svn_error_createf(err->apr_err,
227 err,
228 _("Cannot write lock/entries hashfile '%s'"),
229 svn_dirent_local_style(tmp_path, pool));
230 }
231
232 SVN_ERR(svn_stream_close(stream));
233 SVN_ERR(svn_io_file_rename2(tmp_path, digest_path, FALSE, pool));
234 SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
235 return SVN_NO_ERROR;
236 }
237
238
239 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
240 file (if it exists, and if *LOCK_P is non-NULL) and the hash of
241 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
242 for all allocations. */
243 static svn_error_t *
read_digest_file(apr_hash_t ** children_p,svn_lock_t ** lock_p,const char * fs_path,const char * digest_path,apr_pool_t * pool)244 read_digest_file(apr_hash_t **children_p,
245 svn_lock_t **lock_p,
246 const char *fs_path,
247 const char *digest_path,
248 apr_pool_t *pool)
249 {
250 svn_error_t *err = SVN_NO_ERROR;
251 svn_lock_t *lock;
252 apr_hash_t *hash;
253 svn_stream_t *stream;
254 const char *val;
255 svn_node_kind_t kind;
256
257 if (lock_p)
258 *lock_p = NULL;
259 if (children_p)
260 *children_p = apr_hash_make(pool);
261
262 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
263 if (kind == svn_node_none)
264 return SVN_NO_ERROR;
265
266 /* If our caller doesn't care about anything but the presence of the
267 file... whatever. */
268 if (kind == svn_node_file && !lock_p && !children_p)
269 return SVN_NO_ERROR;
270
271 SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
272
273 hash = apr_hash_make(pool);
274 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
275 {
276 err = svn_error_compose_create(err, svn_stream_close(stream));
277 return svn_error_createf(err->apr_err,
278 err,
279 _("Can't parse lock/entries hashfile '%s'"),
280 svn_dirent_local_style(digest_path, pool));
281 }
282 SVN_ERR(svn_stream_close(stream));
283
284 /* If our caller cares, see if we have a lock path in our hash. If
285 so, we'll assume we have a lock here. */
286 val = hash_fetch(hash, PATH_KEY);
287 if (val && lock_p)
288 {
289 const char *path = val;
290
291 /* Create our lock and load it up. */
292 lock = svn_lock_create(pool);
293 lock->path = path;
294
295 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
296 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
297
298 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
299 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
300
301 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
302 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
303 lock->is_dav_comment = (val[0] == '1');
304
305 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
306 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
307 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
308
309 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
310 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
311
312 lock->comment = hash_fetch(hash, COMMENT_KEY);
313
314 *lock_p = lock;
315 }
316
317 /* If our caller cares, see if we have any children for this path. */
318 val = hash_fetch(hash, CHILDREN_KEY);
319 if (val && children_p)
320 {
321 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
322 int i;
323
324 for (i = 0; i < kiddos->nelts; i++)
325 {
326 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
327 (void *)1);
328 }
329 }
330 return SVN_NO_ERROR;
331 }
332
333
334
335 /*** Lock helper functions (path here are still FS paths, not on-disk
336 schema-supporting paths) ***/
337
338
339 /* Write LOCK in FS to the actual OS filesystem.
340
341 Use PERMS_REFERENCE for the permissions of any digest files.
342 */
343 static svn_error_t *
set_lock(const char * fs_path,svn_lock_t * lock,const char * perms_reference,apr_pool_t * pool)344 set_lock(const char *fs_path,
345 svn_lock_t *lock,
346 const char *perms_reference,
347 apr_pool_t *pool)
348 {
349 const char *digest_path;
350 apr_hash_t *children;
351
352 SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
353
354 /* We could get away without reading the file as children should
355 always come back empty. */
356 SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
357
358 SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
359 perms_reference, pool));
360
361 return SVN_NO_ERROR;
362 }
363
364 static svn_error_t *
delete_lock(const char * fs_path,const char * path,apr_pool_t * pool)365 delete_lock(const char *fs_path,
366 const char *path,
367 apr_pool_t *pool)
368 {
369 const char *digest_path;
370
371 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
372
373 SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
374
375 return SVN_NO_ERROR;
376 }
377
378 static svn_error_t *
add_to_digest(const char * fs_path,apr_array_header_t * paths,const char * index_path,const char * perms_reference,apr_pool_t * pool)379 add_to_digest(const char *fs_path,
380 apr_array_header_t *paths,
381 const char *index_path,
382 const char *perms_reference,
383 apr_pool_t *pool)
384 {
385 const char *index_digest_path;
386 apr_hash_t *children;
387 svn_lock_t *lock;
388 int i;
389 unsigned int original_count;
390
391 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
392
393 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
394
395 original_count = apr_hash_count(children);
396
397 for (i = 0; i < paths->nelts; ++i)
398 {
399 const char *path = APR_ARRAY_IDX(paths, i, const char *);
400 const char *digest_path, *digest_file;
401
402 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
403 digest_file = svn_dirent_basename(digest_path, NULL);
404 svn_hash_sets(children, digest_file, (void *)1);
405 }
406
407 if (apr_hash_count(children) != original_count)
408 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
409 perms_reference, pool));
410
411 return SVN_NO_ERROR;
412 }
413
414 static svn_error_t *
delete_from_digest(const char * fs_path,apr_array_header_t * paths,const char * index_path,const char * perms_reference,apr_pool_t * pool)415 delete_from_digest(const char *fs_path,
416 apr_array_header_t *paths,
417 const char *index_path,
418 const char *perms_reference,
419 apr_pool_t *pool)
420 {
421 const char *index_digest_path;
422 apr_hash_t *children;
423 svn_lock_t *lock;
424 int i;
425
426 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
427
428 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
429
430 for (i = 0; i < paths->nelts; ++i)
431 {
432 const char *path = APR_ARRAY_IDX(paths, i, const char *);
433 const char *digest_path, *digest_file;
434
435 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
436 digest_file = svn_dirent_basename(digest_path, NULL);
437 svn_hash_sets(children, digest_file, NULL);
438 }
439
440 if (apr_hash_count(children) || lock)
441 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
442 perms_reference, pool));
443 else
444 SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
445
446 return SVN_NO_ERROR;
447 }
448
449 static svn_error_t *
450 unlock_single(svn_fs_t *fs,
451 svn_lock_t *lock,
452 apr_pool_t *pool);
453
454 /* Check if LOCK has been already expired. */
lock_expired(const svn_lock_t * lock)455 static svn_boolean_t lock_expired(const svn_lock_t *lock)
456 {
457 return lock->expiration_date && (apr_time_now() > lock->expiration_date);
458 }
459
460 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
461 TRUE if the caller (or one of its callers) has taken out the
462 repository-wide write lock, FALSE otherwise. If MUST_EXIST is
463 not set, the function will simply return NULL in *LOCK_P instead
464 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
465 was not found (much faster). Use POOL for allocations. */
466 static svn_error_t *
get_lock(svn_lock_t ** lock_p,svn_fs_t * fs,const char * path,svn_boolean_t have_write_lock,svn_boolean_t must_exist,apr_pool_t * pool)467 get_lock(svn_lock_t **lock_p,
468 svn_fs_t *fs,
469 const char *path,
470 svn_boolean_t have_write_lock,
471 svn_boolean_t must_exist,
472 apr_pool_t *pool)
473 {
474 svn_lock_t *lock = NULL;
475 const char *digest_path;
476 svn_node_kind_t kind;
477
478 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
479 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
480
481 *lock_p = NULL;
482 if (kind != svn_node_none)
483 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
484
485 if (! lock)
486 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
487
488 /* Don't return an expired lock. */
489 if (lock_expired(lock))
490 {
491 /* Only remove the lock if we have the write lock.
492 Read operations shouldn't change the filesystem. */
493 if (have_write_lock)
494 SVN_ERR(unlock_single(fs, lock, pool));
495 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
496 }
497
498 *lock_p = lock;
499 return SVN_NO_ERROR;
500 }
501
502
503 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
504 TRUE if the caller (or one of its callers) has taken out the
505 repository-wide write lock, FALSE otherwise. Use POOL for
506 allocations. */
507 static svn_error_t *
get_lock_helper(svn_fs_t * fs,svn_lock_t ** lock_p,const char * path,svn_boolean_t have_write_lock,apr_pool_t * pool)508 get_lock_helper(svn_fs_t *fs,
509 svn_lock_t **lock_p,
510 const char *path,
511 svn_boolean_t have_write_lock,
512 apr_pool_t *pool)
513 {
514 svn_lock_t *lock;
515 svn_error_t *err;
516
517 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
518
519 /* We've deliberately decided that this function doesn't tell the
520 caller *why* the lock is unavailable. */
521 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
522 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
523 {
524 svn_error_clear(err);
525 *lock_p = NULL;
526 return SVN_NO_ERROR;
527 }
528 else
529 SVN_ERR(err);
530
531 *lock_p = lock;
532 return SVN_NO_ERROR;
533 }
534
535
536 /* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
537 all locks in and under PATH in FS.
538 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
539 has the FS write lock. */
540 static svn_error_t *
walk_locks(svn_fs_t * fs,const char * digest_path,svn_fs_get_locks_callback_t get_locks_func,void * get_locks_baton,svn_boolean_t have_write_lock,apr_pool_t * pool)541 walk_locks(svn_fs_t *fs,
542 const char *digest_path,
543 svn_fs_get_locks_callback_t get_locks_func,
544 void *get_locks_baton,
545 svn_boolean_t have_write_lock,
546 apr_pool_t *pool)
547 {
548 apr_hash_index_t *hi;
549 apr_hash_t *children;
550 apr_pool_t *subpool;
551 svn_lock_t *lock;
552
553 /* First, send up any locks in the current digest file. */
554 SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
555
556 if (lock && lock_expired(lock))
557 {
558 /* Only remove the lock if we have the write lock.
559 Read operations shouldn't change the filesystem. */
560 if (have_write_lock)
561 SVN_ERR(unlock_single(fs, lock, pool));
562 }
563 else if (lock)
564 {
565 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
566 }
567
568 /* Now, report all the child entries (if any; bail otherwise). */
569 if (! apr_hash_count(children))
570 return SVN_NO_ERROR;
571 subpool = svn_pool_create(pool);
572 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
573 {
574 const char *digest = apr_hash_this_key(hi);
575 svn_pool_clear(subpool);
576
577 SVN_ERR(read_digest_file
578 (NULL, &lock, fs->path,
579 digest_path_from_digest(fs->path, digest, subpool), subpool));
580
581 if (lock && lock_expired(lock))
582 {
583 /* Only remove the lock if we have the write lock.
584 Read operations shouldn't change the filesystem. */
585 if (have_write_lock)
586 SVN_ERR(unlock_single(fs, lock, pool));
587 }
588 else if (lock)
589 {
590 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
591 }
592 }
593 svn_pool_destroy(subpool);
594 return SVN_NO_ERROR;
595 }
596
597
598 /* Utility function: verify that a lock can be used. Interesting
599 errors returned from this function:
600
601 SVN_ERR_FS_NO_USER: No username attached to FS.
602 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
603 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
604 */
605 static svn_error_t *
verify_lock(svn_fs_t * fs,svn_lock_t * lock,apr_pool_t * pool)606 verify_lock(svn_fs_t *fs,
607 svn_lock_t *lock,
608 apr_pool_t *pool)
609 {
610 if ((! fs->access_ctx) || (! fs->access_ctx->username))
611 return svn_error_createf
612 (SVN_ERR_FS_NO_USER, NULL,
613 _("Cannot verify lock on path '%s'; no username available"),
614 lock->path);
615
616 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
617 return svn_error_createf
618 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
619 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
620 fs->access_ctx->username, lock->path, lock->owner);
621
622 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
623 return svn_error_createf
624 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
625 _("Cannot verify lock on path '%s'; no matching lock-token available"),
626 lock->path);
627
628 return SVN_NO_ERROR;
629 }
630
631
632 /* This implements the svn_fs_get_locks_callback_t interface, where
633 BATON is just an svn_fs_t object. */
634 static svn_error_t *
get_locks_callback(void * baton,svn_lock_t * lock,apr_pool_t * pool)635 get_locks_callback(void *baton,
636 svn_lock_t *lock,
637 apr_pool_t *pool)
638 {
639 return verify_lock(baton, lock, pool);
640 }
641
642
643 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
644 svn_error_t *
svn_fs_fs__allow_locked_operation(const char * path,svn_fs_t * fs,svn_boolean_t recurse,svn_boolean_t have_write_lock,apr_pool_t * pool)645 svn_fs_fs__allow_locked_operation(const char *path,
646 svn_fs_t *fs,
647 svn_boolean_t recurse,
648 svn_boolean_t have_write_lock,
649 apr_pool_t *pool)
650 {
651 path = svn_fs__canonicalize_abspath(path, pool);
652 if (recurse)
653 {
654 /* Discover all locks at or below the path. */
655 const char *digest_path;
656 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
657 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
658 fs, have_write_lock, pool));
659 }
660 else
661 {
662 /* Discover and verify any lock attached to the path. */
663 svn_lock_t *lock;
664 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
665 if (lock)
666 SVN_ERR(verify_lock(fs, lock, pool));
667 }
668 return SVN_NO_ERROR;
669 }
670
671 /* Helper function called from the lock and unlock code.
672 UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
673 arrays of child paths. For all of the parent paths of PATH this function
674 adds PATH to the corresponding array of child paths. */
675 static void
schedule_index_update(apr_hash_t * updates,const char * path,apr_pool_t * scratch_pool)676 schedule_index_update(apr_hash_t *updates,
677 const char *path,
678 apr_pool_t *scratch_pool)
679 {
680 apr_pool_t *hashpool = apr_hash_pool_get(updates);
681 const char *parent_path = path;
682
683 while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
684 {
685 apr_array_header_t *children;
686
687 parent_path = svn_fspath__dirname(parent_path, scratch_pool);
688 children = svn_hash_gets(updates, parent_path);
689
690 if (! children)
691 {
692 children = apr_array_make(hashpool, 8, sizeof(const char *));
693 svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
694 }
695
696 APR_ARRAY_PUSH(children, const char *) = path;
697 }
698 }
699
700 /* The effective arguments for lock_body() below. */
701 struct lock_baton {
702 svn_fs_t *fs;
703 apr_array_header_t *targets;
704 apr_array_header_t *infos;
705 const char *comment;
706 svn_boolean_t is_dav_comment;
707 apr_time_t expiration_date;
708 svn_boolean_t steal_lock;
709 apr_pool_t *result_pool;
710 };
711
712 static svn_error_t *
check_lock(svn_error_t ** fs_err,const char * path,const svn_fs_lock_target_t * target,struct lock_baton * lb,svn_fs_root_t * root,svn_revnum_t youngest_rev,apr_pool_t * pool)713 check_lock(svn_error_t **fs_err,
714 const char *path,
715 const svn_fs_lock_target_t *target,
716 struct lock_baton *lb,
717 svn_fs_root_t *root,
718 svn_revnum_t youngest_rev,
719 apr_pool_t *pool)
720 {
721 svn_node_kind_t kind;
722 svn_lock_t *existing_lock;
723
724 *fs_err = SVN_NO_ERROR;
725
726 SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
727 if (kind == svn_node_dir)
728 {
729 *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
730 return SVN_NO_ERROR;
731 }
732
733 /* While our locking implementation easily supports the locking of
734 nonexistent paths, we deliberately choose not to allow such madness. */
735 if (kind == svn_node_none)
736 {
737 if (SVN_IS_VALID_REVNUM(target->current_rev))
738 *fs_err = svn_error_createf(
739 SVN_ERR_FS_OUT_OF_DATE, NULL,
740 _("Path '%s' doesn't exist in HEAD revision"),
741 path);
742 else
743 *fs_err = svn_error_createf(
744 SVN_ERR_FS_NOT_FOUND, NULL,
745 _("Path '%s' doesn't exist in HEAD revision"),
746 path);
747
748 return SVN_NO_ERROR;
749 }
750
751 /* Is the caller attempting to lock an out-of-date working file? */
752 if (SVN_IS_VALID_REVNUM(target->current_rev))
753 {
754 svn_revnum_t created_rev;
755
756 if (target->current_rev > youngest_rev)
757 {
758 *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
759 _("No such revision %ld"),
760 target->current_rev);
761 return SVN_NO_ERROR;
762 }
763
764 SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path,
765 pool));
766
767 /* SVN_INVALID_REVNUM means the path doesn't exist. So
768 apparently somebody is trying to lock something in their
769 working copy, but somebody else has deleted the thing
770 from HEAD. That counts as being 'out of date'. */
771 if (! SVN_IS_VALID_REVNUM(created_rev))
772 {
773 *fs_err = svn_error_createf
774 (SVN_ERR_FS_OUT_OF_DATE, NULL,
775 _("Path '%s' doesn't exist in HEAD revision"), path);
776
777 return SVN_NO_ERROR;
778 }
779
780 if (target->current_rev < created_rev)
781 {
782 *fs_err = svn_error_createf
783 (SVN_ERR_FS_OUT_OF_DATE, NULL,
784 _("Lock failed: newer version of '%s' exists"), path);
785
786 return SVN_NO_ERROR;
787 }
788 }
789
790 /* If the caller provided a TOKEN, we *really* need to see
791 if a lock already exists with that token, and if so, verify that
792 the lock's path matches PATH. Otherwise we run the risk of
793 breaking the 1-to-1 mapping of lock tokens to locked paths. */
794 /* ### TODO: actually do this check. This is tough, because the
795 schema doesn't supply a lookup-by-token mechanism. */
796
797 /* Is the path already locked?
798
799 Note that this next function call will automatically ignore any
800 errors about {the path not existing as a key, the path's token
801 not existing as a key, the lock just having been expired}. And
802 that's totally fine. Any of these three errors are perfectly
803 acceptable to ignore; it means that the path is now free and
804 clear for locking, because the fsfs funcs just cleared out both
805 of the tables for us. */
806 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
807 if (existing_lock)
808 {
809 if (! lb->steal_lock)
810 {
811 /* Sorry, the path is already locked. */
812 *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
813 return SVN_NO_ERROR;
814 }
815 }
816
817 return SVN_NO_ERROR;
818 }
819
820 struct lock_info_t {
821 const char *path;
822 svn_lock_t *lock;
823 svn_error_t *fs_err;
824 };
825
826 /* The body of svn_fs_fs__lock(), which see.
827
828 BATON is a 'struct lock_baton *' holding the effective arguments.
829 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
830 path, mapping canonical path to 'svn_fs_lock_target_t'. Set
831 BATON->infos to an array of 'lock_info_t' holding the results. For
832 the other arguments, see svn_fs_lock_many().
833
834 This implements the svn_fs_fs__with_write_lock() 'body' callback
835 type, and assumes that the write lock is held.
836 */
837 static svn_error_t *
lock_body(void * baton,apr_pool_t * pool)838 lock_body(void *baton, apr_pool_t *pool)
839 {
840 struct lock_baton *lb = baton;
841 svn_fs_root_t *root;
842 svn_revnum_t youngest;
843 const char *rev_0_path;
844 int i;
845 apr_hash_t *index_updates = apr_hash_make(pool);
846 apr_hash_index_t *hi;
847 apr_pool_t *iterpool = svn_pool_create(pool);
848
849 /* Until we implement directory locks someday, we only allow locks
850 on files. */
851 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
852 library dependencies, which are not portable. */
853 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
854 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
855
856 for (i = 0; i < lb->targets->nelts; ++i)
857 {
858 const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
859 svn_sort__item_t);
860 struct lock_info_t info;
861
862 svn_pool_clear(iterpool);
863
864 info.path = item->key;
865 info.lock = NULL;
866 info.fs_err = SVN_NO_ERROR;
867
868 SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
869 youngest, iterpool));
870
871 /* If no error occurred while pre-checking, schedule the index updates for
872 this path. */
873 if (!info.fs_err)
874 schedule_index_update(index_updates, info.path, iterpool);
875
876 APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
877 }
878
879 rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
880
881 /* We apply the scheduled index updates before writing the actual locks.
882
883 Writing indices before locks is correct: if interrupted it leaves
884 indices without locks rather than locks without indices. An
885 index without a lock is consistent in that it always shows up as
886 unlocked in svn_fs_fs__allow_locked_operation. A lock without an
887 index is inconsistent, svn_fs_fs__allow_locked_operation will
888 show locked on the file but unlocked on the parent. */
889
890 for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
891 {
892 const char *path = apr_hash_this_key(hi);
893 apr_array_header_t *children = apr_hash_this_val(hi);
894
895 svn_pool_clear(iterpool);
896 SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
897 iterpool));
898 }
899
900 for (i = 0; i < lb->infos->nelts; ++i)
901 {
902 struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
903 struct lock_info_t);
904 svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
905 svn_fs_lock_target_t *target = item->value;
906
907 svn_pool_clear(iterpool);
908
909 if (! info->fs_err)
910 {
911 info->lock = svn_lock_create(lb->result_pool);
912 if (target->token)
913 info->lock->token = apr_pstrdup(lb->result_pool, target->token);
914 else
915 SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs,
916 lb->result_pool));
917
918 /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
919 of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */
920 info->lock->path = info->path;
921 info->lock->owner = apr_pstrdup(lb->result_pool,
922 lb->fs->access_ctx->username);
923 info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
924 info->lock->is_dav_comment = lb->is_dav_comment;
925 info->lock->creation_date = apr_time_now();
926 info->lock->expiration_date = lb->expiration_date;
927
928 info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
929 iterpool);
930 }
931 }
932
933 svn_pool_destroy(iterpool);
934 return SVN_NO_ERROR;
935 }
936
937 /* The effective arguments for unlock_body() below. */
938 struct unlock_baton {
939 svn_fs_t *fs;
940 apr_array_header_t *targets;
941 apr_array_header_t *infos;
942 /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
943 svn_boolean_t skip_check;
944 svn_boolean_t break_lock;
945 apr_pool_t *result_pool;
946 };
947
948 static svn_error_t *
check_unlock(svn_error_t ** fs_err,const char * path,const char * token,struct unlock_baton * ub,svn_fs_root_t * root,apr_pool_t * pool)949 check_unlock(svn_error_t **fs_err,
950 const char *path,
951 const char *token,
952 struct unlock_baton *ub,
953 svn_fs_root_t *root,
954 apr_pool_t *pool)
955 {
956 svn_lock_t *lock;
957
958 *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
959 if (!*fs_err && !ub->break_lock)
960 {
961 if (strcmp(token, lock->token) != 0)
962 *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
963 else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
964 *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
965 ub->fs->access_ctx->username,
966 lock->owner);
967 }
968
969 return SVN_NO_ERROR;
970 }
971
972 struct unlock_info_t {
973 const char *path;
974 svn_error_t *fs_err;
975 svn_boolean_t done;
976 };
977
978 /* The body of svn_fs_fs__unlock(), which see.
979
980 BATON is a 'struct unlock_baton *' holding the effective arguments.
981 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
982 path, mapping canonical path to (const char *) token. Set
983 BATON->infos to an array of 'unlock_info_t' results. For the other
984 arguments, see svn_fs_unlock_many().
985
986 This implements the svn_fs_fs__with_write_lock() 'body' callback
987 type, and assumes that the write lock is held.
988 */
989 static svn_error_t *
unlock_body(void * baton,apr_pool_t * pool)990 unlock_body(void *baton, apr_pool_t *pool)
991 {
992 struct unlock_baton *ub = baton;
993 svn_fs_root_t *root;
994 svn_revnum_t youngest;
995 const char *rev_0_path;
996 int i;
997 apr_hash_t *indices_updates = apr_hash_make(pool);
998 apr_hash_index_t *hi;
999 apr_pool_t *iterpool = svn_pool_create(pool);
1000
1001 SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1002 SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1003
1004 for (i = 0; i < ub->targets->nelts; ++i)
1005 {
1006 const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1007 svn_sort__item_t);
1008 const char *token = item->value;
1009 struct unlock_info_t info;
1010
1011 svn_pool_clear(iterpool);
1012
1013 info.path = item->key;
1014 info.fs_err = SVN_NO_ERROR;
1015 info.done = FALSE;
1016
1017 if (!ub->skip_check)
1018 SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1019 iterpool));
1020
1021 /* If no error occurred while pre-checking, schedule the index updates for
1022 this path. */
1023 if (!info.fs_err)
1024 schedule_index_update(indices_updates, info.path, iterpool);
1025
1026 APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
1027 }
1028
1029 rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
1030
1031 /* Unlike the lock_body(), we need to delete locks *before* we start to
1032 update indices. */
1033
1034 for (i = 0; i < ub->infos->nelts; ++i)
1035 {
1036 struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
1037 struct unlock_info_t);
1038
1039 svn_pool_clear(iterpool);
1040
1041 if (! info->fs_err)
1042 {
1043 SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1044 info->done = TRUE;
1045 }
1046 }
1047
1048 for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
1049 {
1050 const char *path = apr_hash_this_key(hi);
1051 apr_array_header_t *children = apr_hash_this_val(hi);
1052
1053 svn_pool_clear(iterpool);
1054 SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
1055 iterpool));
1056 }
1057
1058 svn_pool_destroy(iterpool);
1059 return SVN_NO_ERROR;
1060 }
1061
1062 /* Unlock the lock described by LOCK->path and LOCK->token in FS.
1063
1064 This assumes that the write lock is held.
1065 */
1066 static svn_error_t *
unlock_single(svn_fs_t * fs,svn_lock_t * lock,apr_pool_t * pool)1067 unlock_single(svn_fs_t *fs,
1068 svn_lock_t *lock,
1069 apr_pool_t *pool)
1070 {
1071 struct unlock_baton ub;
1072 svn_sort__item_t item;
1073 apr_array_header_t *targets = apr_array_make(pool, 1,
1074 sizeof(svn_sort__item_t));
1075 item.key = lock->path;
1076 item.klen = strlen(item.key);
1077 item.value = (char*)lock->token;
1078 APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1079
1080 ub.fs = fs;
1081 ub.targets = targets;
1082 ub.infos = apr_array_make(pool, targets->nelts,
1083 sizeof(struct unlock_info_t));
1084 ub.skip_check = TRUE;
1085 ub.result_pool = pool;
1086
1087 /* No ub.infos[].fs_err error because skip_check is TRUE. */
1088 SVN_ERR(unlock_body(&ub, pool));
1089
1090 return SVN_NO_ERROR;
1091 }
1092
1093
1094 /*** Public API implementations ***/
1095
1096 svn_error_t *
svn_fs_fs__lock(svn_fs_t * fs,apr_hash_t * targets,const char * comment,svn_boolean_t is_dav_comment,apr_time_t expiration_date,svn_boolean_t steal_lock,svn_fs_lock_callback_t lock_callback,void * lock_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1097 svn_fs_fs__lock(svn_fs_t *fs,
1098 apr_hash_t *targets,
1099 const char *comment,
1100 svn_boolean_t is_dav_comment,
1101 apr_time_t expiration_date,
1102 svn_boolean_t steal_lock,
1103 svn_fs_lock_callback_t lock_callback,
1104 void *lock_baton,
1105 apr_pool_t *result_pool,
1106 apr_pool_t *scratch_pool)
1107 {
1108 struct lock_baton lb;
1109 apr_array_header_t *sorted_targets;
1110 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1111 apr_hash_index_t *hi;
1112 apr_pool_t *iterpool;
1113 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1114 int i;
1115
1116 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1117
1118 /* We need to have a username attached to the fs. */
1119 if (!fs->access_ctx || !fs->access_ctx->username)
1120 return SVN_FS__ERR_NO_USER(fs);
1121
1122 /* The FS locking API allows both canonical and non-canonical
1123 paths which means that the same canonical path could be
1124 represented more than once in the TARGETS hash. We just keep
1125 one, choosing one with a token if possible. */
1126 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1127 {
1128 const char *path = apr_hash_this_key(hi);
1129 const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1130 const svn_fs_lock_target_t *other;
1131
1132 path = svn_fspath__canonicalize(path, result_pool);
1133 other = svn_hash_gets(canonical_targets, path);
1134
1135 if (!other || (!other->token && target->token))
1136 svn_hash_sets(canonical_targets, path, target);
1137 }
1138
1139 sorted_targets = svn_sort__hash(canonical_targets,
1140 svn_sort_compare_items_as_paths,
1141 scratch_pool);
1142
1143 lb.fs = fs;
1144 lb.targets = sorted_targets;
1145 lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
1146 sizeof(struct lock_info_t));
1147 lb.comment = comment;
1148 lb.is_dav_comment = is_dav_comment;
1149 lb.expiration_date = expiration_date;
1150 lb.steal_lock = steal_lock;
1151 lb.result_pool = result_pool;
1152
1153 iterpool = svn_pool_create(scratch_pool);
1154 err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, iterpool);
1155 for (i = 0; i < lb.infos->nelts; ++i)
1156 {
1157 struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1158 struct lock_info_t);
1159 svn_pool_clear(iterpool);
1160 if (!cb_err && lock_callback)
1161 {
1162 if (!info->lock && !info->fs_err)
1163 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1164 0, _("Failed to lock '%s'"),
1165 info->path);
1166
1167 cb_err = lock_callback(lock_baton, info->path, info->lock,
1168 info->fs_err, iterpool);
1169 }
1170 svn_error_clear(info->fs_err);
1171 }
1172 svn_pool_destroy(iterpool);
1173
1174 if (err && cb_err)
1175 svn_error_compose(err, cb_err);
1176 else if (!err)
1177 err = cb_err;
1178
1179 return svn_error_trace(err);
1180 }
1181
1182
1183 svn_error_t *
svn_fs_fs__generate_lock_token(const char ** token,svn_fs_t * fs,apr_pool_t * pool)1184 svn_fs_fs__generate_lock_token(const char **token,
1185 svn_fs_t *fs,
1186 apr_pool_t *pool)
1187 {
1188 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1189
1190 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
1191 want to use the fs UUID + some incremented number? For now, we
1192 generate a URI that matches the DAV RFC. We could change this to
1193 some other URI scheme someday, if we wish. */
1194 *token = apr_pstrcat(pool, "opaquelocktoken:",
1195 svn_uuid_generate(pool), SVN_VA_NULL);
1196 return SVN_NO_ERROR;
1197 }
1198
1199 svn_error_t *
svn_fs_fs__unlock(svn_fs_t * fs,apr_hash_t * targets,svn_boolean_t break_lock,svn_fs_lock_callback_t lock_callback,void * lock_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1200 svn_fs_fs__unlock(svn_fs_t *fs,
1201 apr_hash_t *targets,
1202 svn_boolean_t break_lock,
1203 svn_fs_lock_callback_t lock_callback,
1204 void *lock_baton,
1205 apr_pool_t *result_pool,
1206 apr_pool_t *scratch_pool)
1207 {
1208 struct unlock_baton ub;
1209 apr_array_header_t *sorted_targets;
1210 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1211 apr_hash_index_t *hi;
1212 apr_pool_t *iterpool;
1213 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1214 int i;
1215
1216 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1217
1218 /* We need to have a username attached to the fs. */
1219 if (!fs->access_ctx || !fs->access_ctx->username)
1220 return SVN_FS__ERR_NO_USER(fs);
1221
1222 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1223 {
1224 const char *path = apr_hash_this_key(hi);
1225 const char *token = apr_hash_this_val(hi);
1226 const char *other;
1227
1228 path = svn_fspath__canonicalize(path, result_pool);
1229 other = svn_hash_gets(canonical_targets, path);
1230
1231 if (!other)
1232 svn_hash_sets(canonical_targets, path, token);
1233 }
1234
1235 sorted_targets = svn_sort__hash(canonical_targets,
1236 svn_sort_compare_items_as_paths,
1237 scratch_pool);
1238
1239 ub.fs = fs;
1240 ub.targets = sorted_targets;
1241 ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
1242 sizeof(struct unlock_info_t));
1243 ub.skip_check = FALSE;
1244 ub.break_lock = break_lock;
1245 ub.result_pool = result_pool;
1246
1247 iterpool = svn_pool_create(scratch_pool);
1248 err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool);
1249 for (i = 0; i < ub.infos->nelts; ++i)
1250 {
1251 struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
1252 struct unlock_info_t);
1253 svn_pool_clear(iterpool);
1254 if (!cb_err && lock_callback)
1255 {
1256 if (!info->done && !info->fs_err)
1257 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1258 0, _("Failed to unlock '%s'"),
1259 info->path);
1260 cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1261 iterpool);
1262 }
1263 svn_error_clear(info->fs_err);
1264 }
1265 svn_pool_destroy(iterpool);
1266
1267 if (err && cb_err)
1268 svn_error_compose(err, cb_err);
1269 else if (!err)
1270 err = cb_err;
1271
1272 return svn_error_trace(err);
1273 }
1274
1275
1276 svn_error_t *
svn_fs_fs__get_lock(svn_lock_t ** lock_p,svn_fs_t * fs,const char * path,apr_pool_t * pool)1277 svn_fs_fs__get_lock(svn_lock_t **lock_p,
1278 svn_fs_t *fs,
1279 const char *path,
1280 apr_pool_t *pool)
1281 {
1282 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1283 path = svn_fs__canonicalize_abspath(path, pool);
1284 return get_lock_helper(fs, lock_p, path, FALSE, pool);
1285 }
1286
1287
1288 /* Baton for get_locks_filter_func(). */
1289 typedef struct get_locks_filter_baton_t
1290 {
1291 const char *path;
1292 svn_depth_t requested_depth;
1293 svn_fs_get_locks_callback_t get_locks_func;
1294 void *get_locks_baton;
1295
1296 } get_locks_filter_baton_t;
1297
1298
1299 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
1300 which filters out locks on paths that aren't within
1301 BATON->requested_depth of BATON->path before called
1302 BATON->get_locks_func() with BATON->get_locks_baton.
1303
1304 NOTE: See issue #3660 for details about how the FSFS lock
1305 management code is inconsistent. Until that inconsistency is
1306 resolved, we take this filtering approach rather than honoring
1307 depth requests closer to the crawling code. In other words, once
1308 we decide how to resolve issue #3660, there might be a more
1309 performant way to honor the depth passed to svn_fs_fs__get_locks(). */
1310 static svn_error_t *
get_locks_filter_func(void * baton,svn_lock_t * lock,apr_pool_t * pool)1311 get_locks_filter_func(void *baton,
1312 svn_lock_t *lock,
1313 apr_pool_t *pool)
1314 {
1315 get_locks_filter_baton_t *b = baton;
1316
1317 /* Filter out unwanted paths. Since Subversion only allows
1318 locks on files, we can treat depth=immediates the same as
1319 depth=files for filtering purposes. Meaning, we'll keep
1320 this lock if:
1321
1322 a) its path is the very path we queried, or
1323 b) we've asked for a fully recursive answer, or
1324 c) we've asked for depth=files or depth=immediates, and this
1325 lock is on an immediate child of our query path.
1326 */
1327 if ((strcmp(b->path, lock->path) == 0)
1328 || (b->requested_depth == svn_depth_infinity))
1329 {
1330 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1331 }
1332 else if ((b->requested_depth == svn_depth_files) ||
1333 (b->requested_depth == svn_depth_immediates))
1334 {
1335 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1336 if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1337 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1338 }
1339
1340 return SVN_NO_ERROR;
1341 }
1342
1343 svn_error_t *
svn_fs_fs__get_locks(svn_fs_t * fs,const char * path,svn_depth_t depth,svn_fs_get_locks_callback_t get_locks_func,void * get_locks_baton,apr_pool_t * pool)1344 svn_fs_fs__get_locks(svn_fs_t *fs,
1345 const char *path,
1346 svn_depth_t depth,
1347 svn_fs_get_locks_callback_t get_locks_func,
1348 void *get_locks_baton,
1349 apr_pool_t *pool)
1350 {
1351 const char *digest_path;
1352 get_locks_filter_baton_t glfb;
1353
1354 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1355 path = svn_fs__canonicalize_abspath(path, pool);
1356
1357 glfb.path = path;
1358 glfb.requested_depth = depth;
1359 glfb.get_locks_func = get_locks_func;
1360 glfb.get_locks_baton = get_locks_baton;
1361
1362 /* Get the top digest path in our tree of interest, and then walk it. */
1363 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1364 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1365 FALSE, pool));
1366 return SVN_NO_ERROR;
1367 }
1368