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