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. */
67static svn_error_t *
68make_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. */
86static void
87hash_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). */
105static const char *
106hash_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.  */
115static svn_error_t *
116err_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. */
131static const char *
132digest_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. */
145static svn_error_t *
146digest_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 */
167static svn_error_t *
168write_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.  */
257static svn_error_t *
258read_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 */
357static svn_error_t *
358set_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
380static svn_error_t *
381delete_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
394static svn_error_t *
395add_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
432static svn_error_t *
433delete_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
469static svn_error_t *
470unlock_single(svn_fs_t *fs,
471              svn_lock_t *lock,
472              apr_pool_t *pool);
473
474/* Check if LOCK has been already expired. */
475static 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. */
486static svn_error_t *
487get_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. */
527static svn_error_t *
528get_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. */
560static svn_error_t *
561walk_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 */
624static svn_error_t *
625verify_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. */
652static svn_error_t *
653get_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. */
662svn_error_t *
663svn_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. */
695static void
696schedule_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. */
721typedef 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
732static svn_error_t *
733check_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
840typedef 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 */
857static svn_error_t *
858lock_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. */
959typedef 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
969static svn_error_t *
970check_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
993typedef 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 */
1010static svn_error_t *
1011unlock_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 */
1088static svn_error_t *
1089unlock_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
1118svn_error_t *
1119svn_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
1205svn_error_t *
1206svn_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
1221svn_error_t *
1222svn_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
1297svn_error_t *
1298svn_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(). */
1310typedef 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().  */
1331static svn_error_t *
1332get_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
1364svn_error_t *
1365svn_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