lock.c revision 299742
1/* lock.c :  functions for manipulating filesystem locks.
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23#include "svn_pools.h"
24#include "svn_error.h"
25#include "svn_dirent_uri.h"
26#include "svn_path.h"
27#include "svn_fs.h"
28#include "svn_hash.h"
29#include "svn_time.h"
30#include "svn_utf.h"
31
32#include <apr_uuid.h>
33#include <apr_file_io.h>
34#include <apr_file_info.h>
35
36#include "lock.h"
37#include "tree.h"
38#include "fs_fs.h"
39#include "util.h"
40#include "../libsvn_fs/fs-loader.h"
41
42#include "private/svn_fs_util.h"
43#include "private/svn_fspath.h"
44#include "private/svn_sorts_private.h"
45#include "svn_private_config.h"
46
47/* Names of hash keys used to store a lock for writing to disk. */
48#define PATH_KEY "path"
49#define TOKEN_KEY "token"
50#define OWNER_KEY "owner"
51#define CREATION_DATE_KEY "creation_date"
52#define EXPIRATION_DATE_KEY "expiration_date"
53#define COMMENT_KEY "comment"
54#define IS_DAV_COMMENT_KEY "is_dav_comment"
55#define CHILDREN_KEY "children"
56
57/* Number of characters from the head of a digest file name used to
58   calculate a subdirectory in which to drop that file. */
59#define DIGEST_SUBDIR_LEN 3
60
61
62
63/*** Generic helper functions. ***/
64
65/* Set *DIGEST to the MD5 hash of STR. */
66static svn_error_t *
67make_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. */
85static void
86hash_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). */
104static const char *
105hash_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.  */
114static svn_error_t *
115err_corrupt_lockfile(const char *fs_path, const char *path)
116{
117  return
118    svn_error_createf(
119     SVN_ERR_FS_CORRUPT, 0,
120     _("Corrupt lockfile for path '%s' in filesystem '%s'"),
121     path, fs_path);
122}
123
124
125/*** Digest file handling functions. ***/
126
127/* Return the path of the lock/entries file for which DIGEST is the
128   hashed repository relative path. */
129static const char *
130digest_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. */
143static svn_error_t *
144digest_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 */
165static svn_error_t *
166write_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.  */
243static svn_error_t *
244read_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
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 */
343static svn_error_t *
344set_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
364static svn_error_t *
365delete_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
378static svn_error_t *
379add_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
414static svn_error_t *
415delete_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
449static svn_error_t *
450unlock_single(svn_fs_t *fs,
451              svn_lock_t *lock,
452              apr_pool_t *pool);
453
454/* Check if LOCK has been already expired. */
455static 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. */
466static svn_error_t *
467get_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. */
507static svn_error_t *
508get_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. */
540static svn_error_t *
541walk_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 */
605static svn_error_t *
606verify_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. */
634static svn_error_t *
635get_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. */
644svn_error_t *
645svn_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. */
675static void
676schedule_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. */
701struct 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
712static svn_error_t *
713check_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
820struct 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 */
837static svn_error_t *
838lock_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. */
938struct 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
948static svn_error_t *
949check_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
972struct 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 */
989static svn_error_t *
990unlock_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 */
1066static svn_error_t *
1067unlock_single(svn_fs_t *fs,
1068              svn_lock_t *lock,
1069              apr_pool_t *pool)
1070{
1071  struct unlock_baton ub;
1072  svn_sort__item_t item;
1073  apr_array_header_t *targets = apr_array_make(pool, 1,
1074                                               sizeof(svn_sort__item_t));
1075  item.key = lock->path;
1076  item.klen = strlen(item.key);
1077  item.value = (char*)lock->token;
1078  APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1079
1080  ub.fs = fs;
1081  ub.targets = targets;
1082  ub.infos = apr_array_make(pool, targets->nelts,
1083                            sizeof(struct unlock_info_t));
1084  ub.skip_check = TRUE;
1085  ub.result_pool = pool;
1086
1087  /* No ub.infos[].fs_err error because skip_check is TRUE. */
1088  SVN_ERR(unlock_body(&ub, pool));
1089
1090  return SVN_NO_ERROR;
1091}
1092
1093
1094/*** Public API implementations ***/
1095
1096svn_error_t *
1097svn_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
1183svn_error_t *
1184svn_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
1199svn_error_t *
1200svn_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
1276svn_error_t *
1277svn_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(). */
1289typedef 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().  */
1310static svn_error_t *
1311get_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
1343svn_error_t *
1344svn_fs_fs__get_locks(svn_fs_t *fs,
1345                     const char *path,
1346                     svn_depth_t depth,
1347                     svn_fs_get_locks_callback_t get_locks_func,
1348                     void *get_locks_baton,
1349                     apr_pool_t *pool)
1350{
1351  const char *digest_path;
1352  get_locks_filter_baton_t glfb;
1353
1354  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1355  path = svn_fs__canonicalize_abspath(path, pool);
1356
1357  glfb.path = path;
1358  glfb.requested_depth = depth;
1359  glfb.get_locks_func = get_locks_func;
1360  glfb.get_locks_baton = get_locks_baton;
1361
1362  /* Get the top digest path in our tree of interest, and then walk it. */
1363  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1364  SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1365                     FALSE, pool));
1366  return SVN_NO_ERROR;
1367}
1368