]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/lock.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_fs / lock.c
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 \f
63 /*** Generic helper functions. ***/
64
65 /* Set *DIGEST to the MD5 hash of STR. */
66 static svn_error_t *
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
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 *
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 *
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 \f
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 *
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 *
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 *
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       svn_error_clear(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_rename(tmp_path, digest_path, 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 *
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       svn_error_clear(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 \f
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 *
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 *
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 *
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 *
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. */
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 *
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 *
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 *
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 *
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 *
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 *
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
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 *
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 *
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 or non-existent paths. */
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 *
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 *
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 *
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 \f
1094 /*** Public API implementations ***/
1095
1096 svn_error_t *
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 *
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 *
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 *
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 *
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 *
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 }