]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/lock.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / lock.c
1 /* lock.c :  functions for manipulating filesystem locks.
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include "svn_pools.h"
24 #include "svn_error.h"
25 #include "svn_dirent_uri.h"
26 #include "svn_path.h"
27 #include "svn_fs.h"
28 #include "svn_hash.h"
29 #include "svn_time.h"
30 #include "svn_utf.h"
31
32 #include <apr_uuid.h>
33 #include <apr_file_io.h>
34 #include <apr_file_info.h>
35
36 #include "lock.h"
37 #include "tree.h"
38 #include "fs_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 \f
64 /*** Generic helper functions. ***/
65
66 /* Set *DIGEST to the MD5 hash of STR. */
67 static svn_error_t *
68 make_digest(const char **digest,
69             const char *str,
70             apr_pool_t *pool)
71 {
72   svn_checksum_t *checksum;
73
74   SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
75
76   *digest = svn_checksum_to_cstring_display(checksum, pool);
77   return SVN_NO_ERROR;
78 }
79
80
81 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
82    if unknown) to an svn_string_t-ized version of VALUE (whose size is
83    VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
84    will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
85    is NULL, this function will do nothing. */
86 static void
87 hash_store(apr_hash_t *hash,
88            const char *key,
89            apr_ssize_t key_len,
90            const char *value,
91            apr_ssize_t value_len,
92            apr_pool_t *pool)
93 {
94   if (! (key && value))
95     return;
96   if (value_len == APR_HASH_KEY_STRING)
97     value_len = strlen(value);
98   apr_hash_set(hash, key, key_len,
99                svn_string_ncreate(value, value_len, pool));
100 }
101
102
103 /* Fetch the value of KEY from HASH, returning only the cstring data
104    of that value (if it exists). */
105 static const char *
106 hash_fetch(apr_hash_t *hash,
107            const char *key)
108 {
109   svn_string_t *str = svn_hash_gets(hash, key);
110   return str ? str->data : NULL;
111 }
112
113
114 /* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt.  */
115 static svn_error_t *
116 err_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 \f
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. */
130 static const char *
131 digest_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. */
144 static svn_error_t *
145 digest_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  */
166 static svn_error_t *
167 write_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.  */
256 static svn_error_t *
257 read_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 \f
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  */
356 static svn_error_t *
357 set_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
379 static svn_error_t *
380 delete_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
393 static svn_error_t *
394 add_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
431 static svn_error_t *
432 delete_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
468 static svn_error_t *
469 unlock_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. */
479 static svn_error_t *
480 get_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. */
520 static svn_error_t *
521 get_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(). */
550 typedef 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. */
558 static svn_error_t *
559 locks_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  */
593 typedef 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. */
604 static svn_error_t *
605 walk_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. */
647 static svn_error_t *
648 walk_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  */
673 static svn_error_t *
674 verify_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. */
701 static svn_error_t *
702 get_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. */
711 svn_error_t *
712 svn_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. */
741 typedef 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
752 static svn_error_t *
753 check_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
860 typedef 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  */
878 static svn_error_t *
879 lock_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. */
1028 typedef 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
1038 static svn_error_t *
1039 check_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
1062 typedef 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  */
1081 static svn_error_t *
1082 unlock_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  */
1198 static svn_error_t *
1199 unlock_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 \f
1224 /*** Public API implementations ***/
1225
1226 svn_error_t *
1227 svn_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
1311 svn_error_t *
1312 svn_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
1327 svn_error_t *
1328 svn_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
1401 svn_error_t *
1402 svn_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(). */
1414 typedef 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().  */
1435 static svn_error_t *
1436 get_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
1468 svn_error_t *
1469 svn_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 }