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