1289177Speter/* lock.c :  functions for manipulating filesystem locks.
2289177Speter *
3289177Speter * ====================================================================
4289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
5289177Speter *    or more contributor license agreements.  See the NOTICE file
6289177Speter *    distributed with this work for additional information
7289177Speter *    regarding copyright ownership.  The ASF licenses this file
8289177Speter *    to you under the Apache License, Version 2.0 (the
9289177Speter *    "License"); you may not use this file except in compliance
10289177Speter *    with the License.  You may obtain a copy of the License at
11289177Speter *
12289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
13289177Speter *
14289177Speter *    Unless required by applicable law or agreed to in writing,
15289177Speter *    software distributed under the License is distributed on an
16289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17289177Speter *    KIND, either express or implied.  See the License for the
18289177Speter *    specific language governing permissions and limitations
19289177Speter *    under the License.
20289177Speter * ====================================================================
21289177Speter */
22289177Speter
23289177Speter#include "svn_pools.h"
24289177Speter#include "svn_error.h"
25289177Speter#include "svn_dirent_uri.h"
26289177Speter#include "svn_path.h"
27289177Speter#include "svn_fs.h"
28289177Speter#include "svn_hash.h"
29289177Speter#include "svn_time.h"
30289177Speter#include "svn_utf.h"
31289177Speter
32289177Speter#include <apr_uuid.h>
33289177Speter#include <apr_file_io.h>
34289177Speter#include <apr_file_info.h>
35289177Speter
36289177Speter#include "lock.h"
37289177Speter#include "tree.h"
38289177Speter#include "fs_x.h"
39289177Speter#include "transaction.h"
40289177Speter#include "util.h"
41289177Speter#include "../libsvn_fs/fs-loader.h"
42289177Speter
43289177Speter#include "private/svn_fs_util.h"
44289177Speter#include "private/svn_fspath.h"
45289177Speter#include "private/svn_sorts_private.h"
46289177Speter#include "svn_private_config.h"
47289177Speter
48289177Speter/* Names of hash keys used to store a lock for writing to disk. */
49289177Speter#define PATH_KEY "path"
50289177Speter#define TOKEN_KEY "token"
51289177Speter#define OWNER_KEY "owner"
52289177Speter#define CREATION_DATE_KEY "creation_date"
53289177Speter#define EXPIRATION_DATE_KEY "expiration_date"
54289177Speter#define COMMENT_KEY "comment"
55289177Speter#define IS_DAV_COMMENT_KEY "is_dav_comment"
56289177Speter#define CHILDREN_KEY "children"
57289177Speter
58289177Speter/* Number of characters from the head of a digest file name used to
59289177Speter   calculate a subdirectory in which to drop that file. */
60289177Speter#define DIGEST_SUBDIR_LEN 3
61289177Speter
62289177Speter
63289177Speter
64289177Speter/*** Generic helper functions. ***/
65289177Speter
66289177Speter/* Set *DIGEST to the MD5 hash of STR. */
67289177Speterstatic svn_error_t *
68289177Spetermake_digest(const char **digest,
69289177Speter            const char *str,
70289177Speter            apr_pool_t *pool)
71289177Speter{
72289177Speter  svn_checksum_t *checksum;
73289177Speter
74289177Speter  SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
75289177Speter
76289177Speter  *digest = svn_checksum_to_cstring_display(checksum, pool);
77289177Speter  return SVN_NO_ERROR;
78289177Speter}
79289177Speter
80289177Speter
81289177Speter/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
82289177Speter   if unknown) to an svn_string_t-ized version of VALUE (whose size is
83289177Speter   VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
84289177Speter   will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
85289177Speter   is NULL, this function will do nothing. */
86289177Speterstatic void
87289177Speterhash_store(apr_hash_t *hash,
88289177Speter           const char *key,
89289177Speter           apr_ssize_t key_len,
90289177Speter           const char *value,
91289177Speter           apr_ssize_t value_len,
92289177Speter           apr_pool_t *pool)
93289177Speter{
94289177Speter  if (! (key && value))
95289177Speter    return;
96289177Speter  if (value_len == APR_HASH_KEY_STRING)
97289177Speter    value_len = strlen(value);
98289177Speter  apr_hash_set(hash, key, key_len,
99289177Speter               svn_string_ncreate(value, value_len, pool));
100289177Speter}
101289177Speter
102289177Speter
103289177Speter/* Fetch the value of KEY from HASH, returning only the cstring data
104289177Speter   of that value (if it exists). */
105289177Speterstatic const char *
106289177Speterhash_fetch(apr_hash_t *hash,
107289177Speter           const char *key)
108289177Speter{
109289177Speter  svn_string_t *str = svn_hash_gets(hash, key);
110289177Speter  return str ? str->data : NULL;
111289177Speter}
112289177Speter
113289177Speter
114289177Speter/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt.  */
115289177Speterstatic svn_error_t *
116289177Spetererr_corrupt_lockfile(const char *fs_path, const char *path)
117289177Speter{
118289177Speter  return
119289177Speter    svn_error_createf(
120289177Speter     SVN_ERR_FS_CORRUPT, 0,
121289177Speter     _("Corrupt lockfile for path '%s' in filesystem '%s'"),
122289177Speter     path, fs_path);
123289177Speter}
124289177Speter
125289177Speter
126289177Speter/*** Digest file handling functions. ***/
127289177Speter
128289177Speter/* Return the path of the lock/entries file for which DIGEST is the
129289177Speter   hashed repository relative path. */
130289177Speterstatic const char *
131289177Speterdigest_path_from_digest(const char *fs_path,
132289177Speter                        const char *digest,
133289177Speter                        apr_pool_t *pool)
134289177Speter{
135289177Speter  return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
136289177Speter                              apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
137289177Speter                              digest, SVN_VA_NULL);
138289177Speter}
139289177Speter
140289177Speter
141289177Speter/* Set *DIGEST_PATH to the path to the lock/entries digest file associate
142289177Speter   with PATH, where PATH is the path to the lock file or lock entries file
143289177Speter   in FS. */
144289177Speterstatic svn_error_t *
145289177Speterdigest_path_from_path(const char **digest_path,
146289177Speter                      const char *fs_path,
147289177Speter                      const char *path,
148289177Speter                      apr_pool_t *pool)
149289177Speter{
150289177Speter  const char *digest;
151289177Speter  SVN_ERR(make_digest(&digest, path, pool));
152289177Speter  *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
153289177Speter                                      apr_pstrmemdup(pool, digest,
154289177Speter                                                     DIGEST_SUBDIR_LEN),
155289177Speter                                      digest, SVN_VA_NULL);
156289177Speter  return SVN_NO_ERROR;
157289177Speter}
158289177Speter
159289177Speter
160289177Speter/* Write to DIGEST_PATH a representation of CHILDREN (which may be
161289177Speter   empty, if the versioned path in FS represented by DIGEST_PATH has
162289177Speter   no children) and LOCK (which may be NULL if that versioned path is
163289177Speter   lock itself locked).  Set the permissions of DIGEST_PATH to those of
164289177Speter   PERMS_REFERENCE.  Use POOL for temporary allocations.
165289177Speter */
166289177Speterstatic svn_error_t *
167289177Speterwrite_digest_file(apr_hash_t *children,
168289177Speter                  svn_lock_t *lock,
169289177Speter                  const char *fs_path,
170289177Speter                  const char *digest_path,
171289177Speter                  const char *perms_reference,
172289177Speter                  apr_pool_t *scratch_pool)
173289177Speter{
174289177Speter  svn_error_t *err = SVN_NO_ERROR;
175289177Speter  svn_stream_t *stream;
176289177Speter  apr_hash_index_t *hi;
177289177Speter  apr_hash_t *hash = apr_hash_make(scratch_pool);
178289177Speter  const char *tmp_path;
179289177Speter
180289177Speter  SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
181289177Speter                                                      scratch_pool),
182289177Speter                                      fs_path, scratch_pool));
183289177Speter  SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path,
184289177Speter                                                         scratch_pool),
185289177Speter                                      fs_path, scratch_pool));
186289177Speter
187289177Speter  if (lock)
188289177Speter    {
189289177Speter      const char *creation_date = NULL, *expiration_date = NULL;
190289177Speter      if (lock->creation_date)
191289177Speter        creation_date = svn_time_to_cstring(lock->creation_date,
192289177Speter                                            scratch_pool);
193289177Speter      if (lock->expiration_date)
194289177Speter        expiration_date = svn_time_to_cstring(lock->expiration_date,
195289177Speter                                              scratch_pool);
196289177Speter
197289177Speter      hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
198289177Speter                 lock->path, APR_HASH_KEY_STRING, scratch_pool);
199289177Speter      hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
200289177Speter                 lock->token, APR_HASH_KEY_STRING, scratch_pool);
201289177Speter      hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
202289177Speter                 lock->owner, APR_HASH_KEY_STRING, scratch_pool);
203289177Speter      hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
204289177Speter                 lock->comment, APR_HASH_KEY_STRING, scratch_pool);
205289177Speter      hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
206289177Speter                 lock->is_dav_comment ? "1" : "0", 1, scratch_pool);
207289177Speter      hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
208289177Speter                 creation_date, APR_HASH_KEY_STRING, scratch_pool);
209289177Speter      hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
210289177Speter                 expiration_date, APR_HASH_KEY_STRING, scratch_pool);
211289177Speter    }
212289177Speter  if (apr_hash_count(children))
213289177Speter    {
214289177Speter      svn_stringbuf_t *children_list
215289177Speter        = svn_stringbuf_create_empty(scratch_pool);
216289177Speter      for (hi = apr_hash_first(scratch_pool, children);
217289177Speter           hi;
218289177Speter           hi = apr_hash_next(hi))
219289177Speter        {
220289177Speter          svn_stringbuf_appendbytes(children_list,
221289177Speter                                    apr_hash_this_key(hi),
222289177Speter                                    apr_hash_this_key_len(hi));
223289177Speter          svn_stringbuf_appendbyte(children_list, '\n');
224289177Speter        }
225289177Speter      hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
226289177Speter                 children_list->data, children_list->len, scratch_pool);
227289177Speter    }
228289177Speter
229289177Speter  SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
230289177Speter                                 svn_dirent_dirname(digest_path,
231289177Speter                                                    scratch_pool),
232289177Speter                                 svn_io_file_del_none, scratch_pool,
233289177Speter                                 scratch_pool));
234289177Speter  if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR,
235289177Speter                             scratch_pool)))
236289177Speter    {
237289177Speter      svn_error_clear(svn_stream_close(stream));
238289177Speter      return svn_error_createf(err->apr_err,
239289177Speter                               err,
240289177Speter                               _("Cannot write lock/entries hashfile '%s'"),
241289177Speter                               svn_dirent_local_style(tmp_path,
242289177Speter                                                      scratch_pool));
243289177Speter    }
244289177Speter
245289177Speter  SVN_ERR(svn_stream_close(stream));
246289177Speter  SVN_ERR(svn_io_file_rename(tmp_path, digest_path, scratch_pool));
247289177Speter  SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool));
248289177Speter  return SVN_NO_ERROR;
249289177Speter}
250289177Speter
251289177Speter
252289177Speter/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
253289177Speter   file (if it exists, and if *LOCK_P is non-NULL) and the hash of
254289177Speter   CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL).  Use POOL
255289177Speter   for all allocations.  */
256289177Speterstatic svn_error_t *
257289177Speterread_digest_file(apr_hash_t **children_p,
258289177Speter                 svn_lock_t **lock_p,
259289177Speter                 const char *fs_path,
260289177Speter                 const char *digest_path,
261289177Speter                 apr_pool_t *pool)
262289177Speter{
263289177Speter  svn_error_t *err = SVN_NO_ERROR;
264289177Speter  svn_lock_t *lock;
265289177Speter  apr_hash_t *hash;
266289177Speter  svn_stream_t *stream;
267289177Speter  const char *val;
268289177Speter  svn_node_kind_t kind;
269289177Speter
270289177Speter  if (lock_p)
271289177Speter    *lock_p = NULL;
272289177Speter  if (children_p)
273289177Speter    *children_p = apr_hash_make(pool);
274289177Speter
275289177Speter  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
276289177Speter  if (kind == svn_node_none)
277289177Speter    return SVN_NO_ERROR;
278289177Speter
279289177Speter  /* If our caller doesn't care about anything but the presence of the
280289177Speter     file... whatever. */
281289177Speter  if (kind == svn_node_file && !lock_p && !children_p)
282289177Speter    return SVN_NO_ERROR;
283289177Speter
284289177Speter  SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
285289177Speter
286289177Speter  hash = apr_hash_make(pool);
287289177Speter  if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
288289177Speter    {
289289177Speter      svn_error_clear(svn_stream_close(stream));
290289177Speter      return svn_error_createf(err->apr_err,
291289177Speter                               err,
292289177Speter                               _("Can't parse lock/entries hashfile '%s'"),
293289177Speter                               svn_dirent_local_style(digest_path, pool));
294289177Speter    }
295289177Speter  SVN_ERR(svn_stream_close(stream));
296289177Speter
297289177Speter  /* If our caller cares, see if we have a lock path in our hash. If
298289177Speter     so, we'll assume we have a lock here. */
299289177Speter  val = hash_fetch(hash, PATH_KEY);
300289177Speter  if (val && lock_p)
301289177Speter    {
302289177Speter      const char *path = val;
303289177Speter
304289177Speter      /* Create our lock and load it up. */
305289177Speter      lock = svn_lock_create(pool);
306289177Speter      lock->path = path;
307289177Speter
308289177Speter      if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
309289177Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
310289177Speter
311289177Speter      if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
312289177Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
313289177Speter
314289177Speter      if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
315289177Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
316289177Speter      lock->is_dav_comment = (val[0] == '1');
317289177Speter
318289177Speter      if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
319289177Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
320289177Speter      SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
321289177Speter
322289177Speter      if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
323289177Speter        SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
324289177Speter
325289177Speter      lock->comment = hash_fetch(hash, COMMENT_KEY);
326289177Speter
327289177Speter      *lock_p = lock;
328289177Speter    }
329289177Speter
330289177Speter  /* If our caller cares, see if we have any children for this path. */
331289177Speter  val = hash_fetch(hash, CHILDREN_KEY);
332289177Speter  if (val && children_p)
333289177Speter    {
334289177Speter      apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
335289177Speter      int i;
336289177Speter
337289177Speter      for (i = 0; i < kiddos->nelts; i++)
338289177Speter        {
339289177Speter          svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
340289177Speter                        (void *)1);
341289177Speter        }
342289177Speter    }
343289177Speter  return SVN_NO_ERROR;
344289177Speter}
345289177Speter
346289177Speter
347289177Speter
348289177Speter/*** Lock helper functions (path here are still FS paths, not on-disk
349289177Speter     schema-supporting paths) ***/
350289177Speter
351289177Speter
352289177Speter/* Write LOCK in FS to the actual OS filesystem.
353289177Speter
354289177Speter   Use PERMS_REFERENCE for the permissions of any digest files.
355289177Speter */
356289177Speterstatic svn_error_t *
357289177Speterset_lock(const char *fs_path,
358289177Speter         svn_lock_t *lock,
359289177Speter         const char *perms_reference,
360289177Speter         apr_pool_t *scratch_pool)
361289177Speter{
362289177Speter  const char *digest_path;
363289177Speter  apr_hash_t *children;
364289177Speter
365289177Speter  SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path,
366289177Speter                                scratch_pool));
367289177Speter
368289177Speter  /* We could get away without reading the file as children should
369289177Speter     always come back empty. */
370289177Speter  SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path,
371289177Speter                           scratch_pool));
372289177Speter
373289177Speter  SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
374289177Speter                            perms_reference, scratch_pool));
375289177Speter
376289177Speter  return SVN_NO_ERROR;
377289177Speter}
378289177Speter
379289177Speterstatic svn_error_t *
380289177Speterdelete_lock(const char *fs_path,
381289177Speter            const char *path,
382289177Speter            apr_pool_t *scratch_pool)
383289177Speter{
384289177Speter  const char *digest_path;
385289177Speter
386289177Speter  SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool));
387289177Speter
388289177Speter  SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool));
389289177Speter
390289177Speter  return SVN_NO_ERROR;
391289177Speter}
392289177Speter
393289177Speterstatic svn_error_t *
394289177Speteradd_to_digest(const char *fs_path,
395289177Speter              apr_array_header_t *paths,
396289177Speter              const char *index_path,
397289177Speter              const char *perms_reference,
398289177Speter              apr_pool_t *scratch_pool)
399289177Speter{
400289177Speter  const char *index_digest_path;
401289177Speter  apr_hash_t *children;
402289177Speter  svn_lock_t *lock;
403289177Speter  int i;
404289177Speter  unsigned int original_count;
405289177Speter
406289177Speter  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
407289177Speter                                scratch_pool));
408289177Speter  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
409289177Speter                           scratch_pool));
410289177Speter
411289177Speter  original_count = apr_hash_count(children);
412289177Speter
413289177Speter  for (i = 0; i < paths->nelts; ++i)
414289177Speter    {
415289177Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
416289177Speter      const char *digest_path, *digest_file;
417289177Speter
418289177Speter      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
419289177Speter                                    scratch_pool));
420289177Speter      digest_file = svn_dirent_basename(digest_path, NULL);
421289177Speter      svn_hash_sets(children, digest_file, (void *)1);
422289177Speter    }
423289177Speter
424289177Speter  if (apr_hash_count(children) != original_count)
425289177Speter    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
426289177Speter                              perms_reference, scratch_pool));
427289177Speter
428289177Speter  return SVN_NO_ERROR;
429289177Speter}
430289177Speter
431289177Speterstatic svn_error_t *
432289177Speterdelete_from_digest(const char *fs_path,
433289177Speter                   apr_array_header_t *paths,
434289177Speter                   const char *index_path,
435289177Speter                   const char *perms_reference,
436289177Speter                   apr_pool_t *scratch_pool)
437289177Speter{
438289177Speter  const char *index_digest_path;
439289177Speter  apr_hash_t *children;
440289177Speter  svn_lock_t *lock;
441289177Speter  int i;
442289177Speter
443289177Speter  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
444289177Speter                                scratch_pool));
445289177Speter  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
446289177Speter                           scratch_pool));
447289177Speter
448289177Speter  for (i = 0; i < paths->nelts; ++i)
449289177Speter    {
450289177Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
451289177Speter      const char *digest_path, *digest_file;
452289177Speter
453289177Speter      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
454289177Speter                                    scratch_pool));
455289177Speter      digest_file = svn_dirent_basename(digest_path, NULL);
456289177Speter      svn_hash_sets(children, digest_file, NULL);
457289177Speter    }
458289177Speter
459289177Speter  if (apr_hash_count(children) || lock)
460289177Speter    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
461289177Speter                              perms_reference, scratch_pool));
462289177Speter  else
463289177Speter    SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool));
464289177Speter
465289177Speter  return SVN_NO_ERROR;
466289177Speter}
467289177Speter
468289177Speterstatic svn_error_t *
469289177Speterunlock_single(svn_fs_t *fs,
470289177Speter              svn_lock_t *lock,
471289177Speter              apr_pool_t *pool);
472289177Speter
473289177Speter/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
474289177Speter   TRUE if the caller (or one of its callers) has taken out the
475289177Speter   repository-wide write lock, FALSE otherwise.  If MUST_EXIST is
476289177Speter   not set, the function will simply return NULL in *LOCK_P instead
477289177Speter   of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
478289177Speter   was not found (much faster).  Use POOL for allocations. */
479289177Speterstatic svn_error_t *
480289177Speterget_lock(svn_lock_t **lock_p,
481289177Speter         svn_fs_t *fs,
482289177Speter         const char *path,
483289177Speter         svn_boolean_t have_write_lock,
484289177Speter         svn_boolean_t must_exist,
485289177Speter         apr_pool_t *pool)
486289177Speter{
487289177Speter  svn_lock_t *lock = NULL;
488289177Speter  const char *digest_path;
489289177Speter  svn_node_kind_t kind;
490289177Speter
491289177Speter  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
492289177Speter  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
493289177Speter
494289177Speter  *lock_p = NULL;
495289177Speter  if (kind != svn_node_none)
496289177Speter    SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
497289177Speter
498289177Speter  if (! lock)
499289177Speter    return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
500289177Speter
501289177Speter  /* Don't return an expired lock. */
502289177Speter  if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
503289177Speter    {
504289177Speter      /* Only remove the lock if we have the write lock.
505289177Speter         Read operations shouldn't change the filesystem. */
506289177Speter      if (have_write_lock)
507289177Speter        SVN_ERR(unlock_single(fs, lock, pool));
508289177Speter      return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
509289177Speter    }
510289177Speter
511289177Speter  *lock_p = lock;
512289177Speter  return SVN_NO_ERROR;
513289177Speter}
514289177Speter
515289177Speter
516289177Speter/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
517289177Speter   TRUE if the caller (or one of its callers) has taken out the
518289177Speter   repository-wide write lock, FALSE otherwise.  Use POOL for
519289177Speter   allocations. */
520289177Speterstatic svn_error_t *
521289177Speterget_lock_helper(svn_fs_t *fs,
522289177Speter                svn_lock_t **lock_p,
523289177Speter                const char *path,
524289177Speter                svn_boolean_t have_write_lock,
525289177Speter                apr_pool_t *pool)
526289177Speter{
527289177Speter  svn_lock_t *lock;
528289177Speter  svn_error_t *err;
529289177Speter
530289177Speter  err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
531289177Speter
532289177Speter  /* We've deliberately decided that this function doesn't tell the
533289177Speter     caller *why* the lock is unavailable.  */
534289177Speter  if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
535289177Speter              || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
536289177Speter    {
537289177Speter      svn_error_clear(err);
538289177Speter      *lock_p = NULL;
539289177Speter      return SVN_NO_ERROR;
540289177Speter    }
541289177Speter  else
542289177Speter    SVN_ERR(err);
543289177Speter
544289177Speter  *lock_p = lock;
545289177Speter  return SVN_NO_ERROR;
546289177Speter}
547289177Speter
548289177Speter
549289177Speter/* Baton for locks_walker(). */
550289177Spetertypedef struct walk_locks_baton_t
551289177Speter{
552289177Speter  svn_fs_get_locks_callback_t get_locks_func;
553289177Speter  void *get_locks_baton;
554289177Speter  svn_fs_t *fs;
555289177Speter} walk_locks_baton_t;
556289177Speter
557289177Speter/* Implements walk_digests_callback_t. */
558289177Speterstatic svn_error_t *
559289177Speterlocks_walker(void *baton,
560289177Speter             const char *fs_path,
561289177Speter             const char *digest_path,
562289177Speter             svn_lock_t *lock,
563289177Speter             svn_boolean_t have_write_lock,
564289177Speter             apr_pool_t *pool)
565289177Speter{
566289177Speter  walk_locks_baton_t *wlb = baton;
567289177Speter
568289177Speter  if (lock)
569289177Speter    {
570289177Speter      /* Don't report an expired lock. */
571289177Speter      if (lock->expiration_date == 0
572289177Speter          || (apr_time_now() <= lock->expiration_date))
573289177Speter        {
574289177Speter          if (wlb->get_locks_func)
575289177Speter            SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
576289177Speter        }
577289177Speter      else
578289177Speter        {
579289177Speter          /* Only remove the lock if we have the write lock.
580289177Speter             Read operations shouldn't change the filesystem. */
581289177Speter          if (have_write_lock)
582289177Speter            SVN_ERR(unlock_single(wlb->fs, lock, pool));
583289177Speter        }
584289177Speter    }
585289177Speter
586289177Speter  return SVN_NO_ERROR;
587289177Speter}
588289177Speter
589289177Speter/* Callback type for walk_digest_files().
590289177Speter *
591289177Speter * LOCK come from a read_digest_file(digest_path) call.
592289177Speter */
593289177Spetertypedef svn_error_t *(*walk_digests_callback_t)(void *baton,
594289177Speter                                                const char *fs_path,
595289177Speter                                                const char *digest_path,
596289177Speter                                                svn_lock_t *lock,
597289177Speter                                                svn_boolean_t have_write_lock,
598289177Speter                                                apr_pool_t *pool);
599289177Speter
600289177Speter/* A function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
601289177Speter   all lock digest files in and under PATH in FS.
602289177Speter   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
603289177Speter   has the FS write lock. */
604289177Speterstatic svn_error_t *
605289177Speterwalk_digest_files(const char *fs_path,
606289177Speter                  const char *digest_path,
607289177Speter                  walk_digests_callback_t walk_digests_func,
608289177Speter                  void *walk_digests_baton,
609289177Speter                  svn_boolean_t have_write_lock,
610289177Speter                  apr_pool_t *pool)
611289177Speter{
612289177Speter  apr_hash_index_t *hi;
613289177Speter  apr_hash_t *children;
614289177Speter  apr_pool_t *subpool;
615289177Speter  svn_lock_t *lock;
616289177Speter
617289177Speter  /* First, send up any locks in the current digest file. */
618289177Speter  SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
619289177Speter
620289177Speter  SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock,
621289177Speter                            have_write_lock, pool));
622289177Speter
623289177Speter  /* Now, report all the child entries (if any; bail otherwise). */
624289177Speter  if (! apr_hash_count(children))
625289177Speter    return SVN_NO_ERROR;
626289177Speter  subpool = svn_pool_create(pool);
627289177Speter  for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
628289177Speter    {
629289177Speter      const char *digest = apr_hash_this_key(hi);
630289177Speter      svn_pool_clear(subpool);
631289177Speter
632289177Speter      SVN_ERR(read_digest_file
633289177Speter              (NULL, &lock, fs_path,
634289177Speter               digest_path_from_digest(fs_path, digest, subpool), subpool));
635289177Speter
636289177Speter      SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock,
637289177Speter                                have_write_lock, subpool));
638289177Speter    }
639289177Speter  svn_pool_destroy(subpool);
640289177Speter  return SVN_NO_ERROR;
641289177Speter}
642289177Speter
643289177Speter/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
644289177Speter   all locks in and under PATH in FS.
645289177Speter   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
646289177Speter   has the FS write lock. */
647289177Speterstatic svn_error_t *
648289177Speterwalk_locks(svn_fs_t *fs,
649289177Speter           const char *digest_path,
650289177Speter           svn_fs_get_locks_callback_t get_locks_func,
651289177Speter           void *get_locks_baton,
652289177Speter           svn_boolean_t have_write_lock,
653289177Speter           apr_pool_t *pool)
654289177Speter{
655289177Speter  walk_locks_baton_t wlb;
656289177Speter
657289177Speter  wlb.get_locks_func = get_locks_func;
658289177Speter  wlb.get_locks_baton = get_locks_baton;
659289177Speter  wlb.fs = fs;
660289177Speter  SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
661289177Speter                            have_write_lock, pool));
662289177Speter  return SVN_NO_ERROR;
663289177Speter}
664289177Speter
665289177Speter
666289177Speter/* Utility function:  verify that a lock can be used.  Interesting
667289177Speter   errors returned from this function:
668289177Speter
669289177Speter      SVN_ERR_FS_NO_USER: No username attached to FS.
670289177Speter      SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
671289177Speter      SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
672289177Speter */
673289177Speterstatic svn_error_t *
674289177Speterverify_lock(svn_fs_t *fs,
675289177Speter            svn_lock_t *lock)
676289177Speter{
677289177Speter  if ((! fs->access_ctx) || (! fs->access_ctx->username))
678289177Speter    return svn_error_createf
679289177Speter      (SVN_ERR_FS_NO_USER, NULL,
680289177Speter       _("Cannot verify lock on path '%s'; no username available"),
681289177Speter       lock->path);
682289177Speter
683289177Speter  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
684289177Speter    return svn_error_createf
685289177Speter      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
686289177Speter       _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
687289177Speter       fs->access_ctx->username, lock->path, lock->owner);
688289177Speter
689289177Speter  else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
690289177Speter    return svn_error_createf
691289177Speter      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
692289177Speter       _("Cannot verify lock on path '%s'; no matching lock-token available"),
693289177Speter       lock->path);
694289177Speter
695289177Speter  return SVN_NO_ERROR;
696289177Speter}
697289177Speter
698289177Speter
699289177Speter/* This implements the svn_fs_get_locks_callback_t interface, where
700289177Speter   BATON is just an svn_fs_t object. */
701289177Speterstatic svn_error_t *
702289177Speterget_locks_callback(void *baton,
703289177Speter                   svn_lock_t *lock,
704289177Speter                   apr_pool_t *pool)
705289177Speter{
706289177Speter  return verify_lock(baton, lock);
707289177Speter}
708289177Speter
709289177Speter
710289177Speter/* The main routine for lock enforcement, used throughout libsvn_fs_x. */
711289177Spetersvn_error_t *
712289177Spetersvn_fs_x__allow_locked_operation(const char *path,
713289177Speter                                 svn_fs_t *fs,
714289177Speter                                 svn_boolean_t recurse,
715289177Speter                                 svn_boolean_t have_write_lock,
716289177Speter                                 apr_pool_t *scratch_pool)
717289177Speter{
718289177Speter  path = svn_fs__canonicalize_abspath(path, scratch_pool);
719289177Speter  if (recurse)
720289177Speter    {
721289177Speter      /* Discover all locks at or below the path. */
722289177Speter      const char *digest_path;
723289177Speter      SVN_ERR(digest_path_from_path(&digest_path, fs->path, path,
724289177Speter                                    scratch_pool));
725289177Speter      SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
726289177Speter                         fs, have_write_lock, scratch_pool));
727289177Speter    }
728289177Speter  else
729289177Speter    {
730289177Speter      /* Discover and verify any lock attached to the path. */
731289177Speter      svn_lock_t *lock;
732289177Speter      SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock,
733289177Speter                              scratch_pool));
734289177Speter      if (lock)
735289177Speter        SVN_ERR(verify_lock(fs, lock));
736289177Speter    }
737289177Speter  return SVN_NO_ERROR;
738289177Speter}
739289177Speter
740289177Speter/* The effective arguments for lock_body() below. */
741289177Spetertypedef struct lock_baton_t {
742289177Speter  svn_fs_t *fs;
743289177Speter  apr_array_header_t *targets;
744289177Speter  apr_array_header_t *infos;
745289177Speter  const char *comment;
746289177Speter  svn_boolean_t is_dav_comment;
747289177Speter  apr_time_t expiration_date;
748289177Speter  svn_boolean_t steal_lock;
749289177Speter  apr_pool_t *result_pool;
750289177Speter} lock_baton_t;
751289177Speter
752289177Speterstatic svn_error_t *
753289177Spetercheck_lock(svn_error_t **fs_err,
754289177Speter           const char *path,
755289177Speter           const svn_fs_lock_target_t *target,
756289177Speter           lock_baton_t *lb,
757289177Speter           svn_fs_root_t *root,
758289177Speter           svn_revnum_t youngest_rev,
759289177Speter           apr_pool_t *pool)
760289177Speter{
761289177Speter  svn_node_kind_t kind;
762289177Speter  svn_lock_t *existing_lock;
763289177Speter
764289177Speter  *fs_err = SVN_NO_ERROR;
765289177Speter
766289177Speter  SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool));
767289177Speter  if (kind == svn_node_dir)
768289177Speter    {
769289177Speter      *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
770289177Speter      return SVN_NO_ERROR;
771289177Speter    }
772289177Speter
773289177Speter  /* While our locking implementation easily supports the locking of
774289177Speter     nonexistent paths, we deliberately choose not to allow such madness. */
775289177Speter  if (kind == svn_node_none)
776289177Speter    {
777289177Speter      if (SVN_IS_VALID_REVNUM(target->current_rev))
778289177Speter        *fs_err = svn_error_createf(
779289177Speter          SVN_ERR_FS_OUT_OF_DATE, NULL,
780289177Speter          _("Path '%s' doesn't exist in HEAD revision"),
781289177Speter          path);
782289177Speter      else
783289177Speter        *fs_err = svn_error_createf(
784289177Speter          SVN_ERR_FS_NOT_FOUND, NULL,
785289177Speter          _("Path '%s' doesn't exist in HEAD revision"),
786289177Speter          path);
787289177Speter
788289177Speter      return SVN_NO_ERROR;
789289177Speter    }
790289177Speter
791289177Speter  /* Is the caller attempting to lock an out-of-date working file? */
792289177Speter  if (SVN_IS_VALID_REVNUM(target->current_rev))
793289177Speter    {
794289177Speter      svn_revnum_t created_rev;
795289177Speter
796289177Speter      if (target->current_rev > youngest_rev)
797289177Speter        {
798289177Speter          *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
799289177Speter                                      _("No such revision %ld"),
800289177Speter                                      target->current_rev);
801289177Speter          return SVN_NO_ERROR;
802289177Speter        }
803289177Speter
804289177Speter      SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
805289177Speter                                         pool));
806289177Speter
807289177Speter      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
808289177Speter         apparently somebody is trying to lock something in their
809289177Speter         working copy, but somebody else has deleted the thing
810289177Speter         from HEAD.  That counts as being 'out of date'. */
811289177Speter      if (! SVN_IS_VALID_REVNUM(created_rev))
812289177Speter        {
813289177Speter          *fs_err = svn_error_createf
814289177Speter            (SVN_ERR_FS_OUT_OF_DATE, NULL,
815289177Speter             _("Path '%s' doesn't exist in HEAD revision"), path);
816289177Speter
817289177Speter          return SVN_NO_ERROR;
818289177Speter        }
819289177Speter
820289177Speter      if (target->current_rev < created_rev)
821289177Speter        {
822289177Speter          *fs_err = svn_error_createf
823289177Speter            (SVN_ERR_FS_OUT_OF_DATE, NULL,
824289177Speter             _("Lock failed: newer version of '%s' exists"), path);
825289177Speter
826289177Speter          return SVN_NO_ERROR;
827289177Speter        }
828289177Speter    }
829289177Speter
830289177Speter  /* If the caller provided a TOKEN, we *really* need to see
831289177Speter     if a lock already exists with that token, and if so, verify that
832289177Speter     the lock's path matches PATH.  Otherwise we run the risk of
833289177Speter     breaking the 1-to-1 mapping of lock tokens to locked paths. */
834289177Speter  /* ### TODO:  actually do this check.  This is tough, because the
835289177Speter     schema doesn't supply a lookup-by-token mechanism. */
836289177Speter
837289177Speter  /* Is the path already locked?
838289177Speter
839289177Speter     Note that this next function call will automatically ignore any
840289177Speter     errors about {the path not existing as a key, the path's token
841289177Speter     not existing as a key, the lock just having been expired}.  And
842289177Speter     that's totally fine.  Any of these three errors are perfectly
843289177Speter     acceptable to ignore; it means that the path is now free and
844289177Speter     clear for locking, because the fsx funcs just cleared out both
845289177Speter     of the tables for us.   */
846289177Speter  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
847289177Speter  if (existing_lock)
848289177Speter    {
849289177Speter      if (! lb->steal_lock)
850289177Speter        {
851289177Speter          /* Sorry, the path is already locked. */
852289177Speter          *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
853289177Speter          return SVN_NO_ERROR;
854289177Speter        }
855289177Speter    }
856289177Speter
857289177Speter  return SVN_NO_ERROR;
858289177Speter}
859289177Speter
860289177Spetertypedef struct lock_info_t {
861289177Speter  const char *path;
862289177Speter  const char *component;
863289177Speter  svn_lock_t *lock;
864289177Speter  svn_error_t *fs_err;
865289177Speter} lock_info_t;
866289177Speter
867289177Speter/* The body of svn_fs_x__lock(), which see.
868289177Speter
869289177Speter   BATON is a 'lock_baton_t *' holding the effective arguments.
870289177Speter   BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
871289177Speter   path, mapping canonical path to 'svn_fs_lock_target_t'.  Set
872289177Speter   BATON->infos to an array of 'lock_info_t' holding the results.  For
873289177Speter   the other arguments, see svn_fs_lock_many().
874289177Speter
875289177Speter   This implements the svn_fs_x__with_write_lock() 'body' callback
876289177Speter   type, and assumes that the write lock is held.
877289177Speter */
878289177Speterstatic svn_error_t *
879289177Speterlock_body(void *baton, apr_pool_t *pool)
880289177Speter{
881289177Speter  lock_baton_t *lb = baton;
882289177Speter  svn_fs_root_t *root;
883289177Speter  svn_revnum_t youngest;
884289177Speter  const char *rev_0_path;
885289177Speter  int i, outstanding = 0;
886289177Speter  apr_pool_t *iterpool = svn_pool_create(pool);
887289177Speter
888289177Speter  lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
889289177Speter                             sizeof(lock_info_t));
890289177Speter
891289177Speter  /* Until we implement directory locks someday, we only allow locks
892289177Speter     on files or non-existent paths. */
893289177Speter  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
894289177Speter     library dependencies, which are not portable. */
895289177Speter  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
896289177Speter  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
897289177Speter
898289177Speter  for (i = 0; i < lb->targets->nelts; ++i)
899289177Speter    {
900289177Speter      const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
901289177Speter                                                    svn_sort__item_t);
902289177Speter      const svn_fs_lock_target_t *target = item->value;
903289177Speter      lock_info_t info;
904289177Speter
905289177Speter      svn_pool_clear(iterpool);
906289177Speter
907289177Speter      info.path = item->key;
908289177Speter      SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root,
909289177Speter                         youngest, iterpool));
910289177Speter      info.lock = NULL;
911289177Speter      info.component = NULL;
912289177Speter      APR_ARRAY_PUSH(lb->infos, lock_info_t) = info;
913289177Speter      if (!info.fs_err)
914289177Speter        ++outstanding;
915289177Speter    }
916289177Speter
917289177Speter  rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
918289177Speter
919289177Speter  /* Given the paths:
920289177Speter
921289177Speter       /foo/bar/f
922289177Speter       /foo/bar/g
923289177Speter       /zig/x
924289177Speter
925289177Speter     we loop through repeatedly.  The first pass sees '/' on all paths
926289177Speter     and writes the '/' index.  The second pass sees '/foo' twice and
927289177Speter     writes that index followed by '/zig' and that index. The third
928289177Speter     pass sees '/foo/bar' twice and writes that index, and then writes
929289177Speter     the lock for '/zig/x'.  The fourth pass writes the locks for
930289177Speter     '/foo/bar/f' and '/foo/bar/g'.
931289177Speter
932289177Speter     Writing indices before locks is correct: if interrupted it leaves
933289177Speter     indices without locks rather than locks without indices.  An
934289177Speter     index without a lock is consistent in that it always shows up as
935289177Speter     unlocked in svn_fs_x__allow_locked_operation.  A lock without an
936289177Speter     index is inconsistent, svn_fs_x__allow_locked_operation will
937289177Speter     show locked on the file but unlocked on the parent. */
938289177Speter
939289177Speter
940289177Speter  while (outstanding)
941289177Speter    {
942289177Speter      const char *last_path = NULL;
943289177Speter      apr_array_header_t *paths;
944289177Speter
945289177Speter      svn_pool_clear(iterpool);
946289177Speter      paths = apr_array_make(iterpool, 1, sizeof(const char *));
947289177Speter
948289177Speter      for (i = 0; i < lb->infos->nelts; ++i)
949289177Speter        {
950289177Speter          lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i, lock_info_t);
951289177Speter          const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
952289177Speter                                                        svn_sort__item_t);
953289177Speter          const svn_fs_lock_target_t *target = item->value;
954289177Speter
955289177Speter          if (!info->fs_err && !info->lock)
956289177Speter            {
957289177Speter              if (!info->component)
958289177Speter                {
959289177Speter                  info->component = info->path;
960289177Speter                  APR_ARRAY_PUSH(paths, const char *) = info->path;
961289177Speter                  last_path = "/";
962289177Speter                }
963289177Speter              else
964289177Speter                {
965289177Speter                  info->component = strchr(info->component + 1, '/');
966289177Speter                  if (!info->component)
967289177Speter                    {
968289177Speter                      /* The component is a path to lock, this cannot
969289177Speter                         match a previous path that need to be indexed. */
970289177Speter                      if (paths->nelts)
971289177Speter                        {
972289177Speter                          SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
973289177Speter                                                rev_0_path, iterpool));
974289177Speter                          apr_array_clear(paths);
975289177Speter                          last_path = NULL;
976289177Speter                        }
977289177Speter
978289177Speter                      info->lock = svn_lock_create(lb->result_pool);
979289177Speter                      if (target->token)
980289177Speter                        info->lock->token = target->token;
981289177Speter                      else
982289177Speter                        SVN_ERR(svn_fs_x__generate_lock_token(
983289177Speter                                  &(info->lock->token), lb->fs,
984289177Speter                                  lb->result_pool));
985289177Speter                      info->lock->path = info->path;
986289177Speter                      info->lock->owner = lb->fs->access_ctx->username;
987289177Speter                      info->lock->comment = lb->comment;
988289177Speter                      info->lock->is_dav_comment = lb->is_dav_comment;
989289177Speter                      info->lock->creation_date = apr_time_now();
990289177Speter                      info->lock->expiration_date = lb->expiration_date;
991289177Speter
992289177Speter                      info->fs_err = set_lock(lb->fs->path, info->lock,
993289177Speter                                              rev_0_path, iterpool);
994289177Speter                      --outstanding;
995289177Speter                    }
996289177Speter                  else
997289177Speter                    {
998289177Speter                      /* The component is a path to an index. */
999289177Speter                      apr_size_t len = info->component - info->path;
1000289177Speter
1001289177Speter                      if (last_path
1002289177Speter                          && (strncmp(last_path, info->path, len)
1003289177Speter                              || strlen(last_path) != len))
1004289177Speter                        {
1005289177Speter                          /* No match to the previous paths to index. */
1006289177Speter                          SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
1007289177Speter                                                rev_0_path, iterpool));
1008289177Speter                          apr_array_clear(paths);
1009289177Speter                          last_path = NULL;
1010289177Speter                        }
1011289177Speter                      APR_ARRAY_PUSH(paths, const char *) = info->path;
1012289177Speter                      if (!last_path)
1013289177Speter                        last_path = apr_pstrndup(iterpool, info->path, len);
1014289177Speter                    }
1015289177Speter                }
1016289177Speter            }
1017289177Speter
1018289177Speter          if (last_path && i == lb->infos->nelts - 1)
1019289177Speter            SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
1020289177Speter                                  rev_0_path, iterpool));
1021289177Speter        }
1022289177Speter    }
1023289177Speter
1024289177Speter  return SVN_NO_ERROR;
1025289177Speter}
1026289177Speter
1027289177Speter/* The effective arguments for unlock_body() below. */
1028289177Spetertypedef struct unlock_baton_t {
1029289177Speter  svn_fs_t *fs;
1030289177Speter  apr_array_header_t *targets;
1031289177Speter  apr_array_header_t *infos;
1032289177Speter  /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
1033289177Speter  svn_boolean_t skip_check;
1034289177Speter  svn_boolean_t break_lock;
1035289177Speter  apr_pool_t *result_pool;
1036289177Speter} unlock_baton_t;
1037289177Speter
1038289177Speterstatic svn_error_t *
1039289177Spetercheck_unlock(svn_error_t **fs_err,
1040289177Speter             const char *path,
1041289177Speter             const char *token,
1042289177Speter             unlock_baton_t *ub,
1043289177Speter             svn_fs_root_t *root,
1044289177Speter             apr_pool_t *pool)
1045289177Speter{
1046289177Speter  svn_lock_t *lock;
1047289177Speter
1048289177Speter  *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
1049289177Speter  if (!*fs_err && !ub->break_lock)
1050289177Speter    {
1051289177Speter      if (strcmp(token, lock->token) != 0)
1052289177Speter        *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
1053289177Speter      else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
1054289177Speter        *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
1055289177Speter                                                  ub->fs->access_ctx->username,
1056289177Speter                                                  lock->owner);
1057289177Speter    }
1058289177Speter
1059289177Speter  return SVN_NO_ERROR;
1060289177Speter}
1061289177Speter
1062289177Spetertypedef struct unlock_info_t {
1063289177Speter  const char *path;
1064289177Speter  const char *component;
1065289177Speter  svn_error_t *fs_err;
1066289177Speter  svn_boolean_t done;
1067289177Speter  int components;
1068289177Speter} unlock_info_t;
1069289177Speter
1070289177Speter/* The body of svn_fs_x__unlock(), which see.
1071289177Speter
1072289177Speter   BATON is a 'unlock_baton_t *' holding the effective arguments.
1073289177Speter   BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
1074289177Speter   path, mapping canonical path to (const char *) token.  Set
1075289177Speter   BATON->infos to an array of 'unlock_info_t' results.  For the other
1076289177Speter   arguments, see svn_fs_unlock_many().
1077289177Speter
1078289177Speter   This implements the svn_fs_x__with_write_lock() 'body' callback
1079289177Speter   type, and assumes that the write lock is held.
1080289177Speter */
1081289177Speterstatic svn_error_t *
1082289177Speterunlock_body(void *baton, apr_pool_t *pool)
1083289177Speter{
1084289177Speter  unlock_baton_t *ub = baton;
1085289177Speter  svn_fs_root_t *root;
1086289177Speter  svn_revnum_t youngest;
1087289177Speter  const char *rev_0_path;
1088289177Speter  int i, max_components = 0, outstanding = 0;
1089289177Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1090289177Speter
1091289177Speter  ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
1092289177Speter                             sizeof( unlock_info_t));
1093289177Speter
1094289177Speter  SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1095289177Speter  SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1096289177Speter
1097289177Speter  for (i = 0; i < ub->targets->nelts; ++i)
1098289177Speter    {
1099289177Speter      const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1100289177Speter                                                    svn_sort__item_t);
1101289177Speter      const char *token = item->value;
1102289177Speter      unlock_info_t info = { 0 };
1103289177Speter
1104289177Speter      svn_pool_clear(iterpool);
1105289177Speter
1106289177Speter      info.path = item->key;
1107289177Speter      if (!ub->skip_check)
1108289177Speter        SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1109289177Speter                             iterpool));
1110289177Speter      if (!info.fs_err)
1111289177Speter        {
1112289177Speter          const char *s;
1113289177Speter
1114289177Speter          info.components = 1;
1115289177Speter          info.component = info.path;
1116289177Speter          while((s = strchr(info.component + 1, '/')))
1117289177Speter            {
1118289177Speter              info.component = s;
1119289177Speter              ++info.components;
1120289177Speter            }
1121289177Speter
1122289177Speter          if (info.components > max_components)
1123289177Speter            max_components = info.components;
1124289177Speter
1125289177Speter          ++outstanding;
1126289177Speter        }
1127289177Speter      APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info;
1128289177Speter    }
1129289177Speter
1130289177Speter  rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
1131289177Speter
1132289177Speter  for (i = max_components; i >= 0; --i)
1133289177Speter    {
1134289177Speter      const char *last_path = NULL;
1135289177Speter      apr_array_header_t *paths;
1136289177Speter      int j;
1137289177Speter
1138289177Speter      svn_pool_clear(iterpool);
1139289177Speter      paths = apr_array_make(pool, 1, sizeof(const char *));
1140289177Speter
1141289177Speter      for (j = 0; j < ub->infos->nelts; ++j)
1142289177Speter        {
1143289177Speter          unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j, unlock_info_t);
1144289177Speter
1145289177Speter          if (!info->fs_err && info->path)
1146289177Speter            {
1147289177Speter
1148289177Speter              if (info->components == i)
1149289177Speter                {
1150289177Speter                  SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1151289177Speter                  info->done = TRUE;
1152289177Speter                }
1153289177Speter              else if (info->components > i)
1154289177Speter                {
1155289177Speter                  apr_size_t len = info->component - info->path;
1156289177Speter
1157289177Speter                  if (last_path
1158289177Speter                      && strcmp(last_path, "/")
1159289177Speter                      && (strncmp(last_path, info->path, len)
1160289177Speter                          || strlen(last_path) != len))
1161289177Speter                    {
1162289177Speter                      SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
1163289177Speter                                                 rev_0_path, iterpool));
1164289177Speter                      apr_array_clear(paths);
1165289177Speter                      last_path = NULL;
1166289177Speter                    }
1167289177Speter                  APR_ARRAY_PUSH(paths, const char *) = info->path;
1168289177Speter                  if (!last_path)
1169289177Speter                    {
1170289177Speter                      if (info->component > info->path)
1171289177Speter                        last_path = apr_pstrndup(pool, info->path, len);
1172289177Speter                      else
1173289177Speter                        last_path = "/";
1174289177Speter                    }
1175289177Speter
1176289177Speter                  if (info->component > info->path)
1177289177Speter                    {
1178289177Speter                      --info->component;
1179289177Speter                      while(info->component[0] != '/')
1180289177Speter                        --info->component;
1181289177Speter                    }
1182289177Speter                }
1183289177Speter            }
1184289177Speter
1185289177Speter          if (last_path && j == ub->infos->nelts - 1)
1186289177Speter            SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
1187289177Speter                                       rev_0_path, iterpool));
1188289177Speter        }
1189289177Speter    }
1190289177Speter
1191289177Speter  return SVN_NO_ERROR;
1192289177Speter}
1193289177Speter
1194289177Speter/* Unlock the lock described by LOCK->path and LOCK->token in FS.
1195289177Speter
1196289177Speter   This assumes that the write lock is held.
1197289177Speter */
1198289177Speterstatic svn_error_t *
1199289177Speterunlock_single(svn_fs_t *fs,
1200289177Speter              svn_lock_t *lock,
1201289177Speter              apr_pool_t *scratch_pool)
1202289177Speter{
1203289177Speter  unlock_baton_t ub;
1204289177Speter  svn_sort__item_t item;
1205289177Speter  apr_array_header_t *targets = apr_array_make(scratch_pool, 1,
1206289177Speter                                               sizeof(svn_sort__item_t));
1207289177Speter  item.key = lock->path;
1208289177Speter  item.klen = strlen(item.key);
1209289177Speter  item.value = (char*)lock->token;
1210289177Speter  APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1211289177Speter
1212289177Speter  ub.fs = fs;
1213289177Speter  ub.targets = targets;
1214289177Speter  ub.skip_check = TRUE;
1215289177Speter  ub.result_pool = scratch_pool;
1216289177Speter
1217289177Speter  /* No ub.infos[].fs_err error because skip_check is TRUE. */
1218289177Speter  SVN_ERR(unlock_body(&ub, scratch_pool));
1219289177Speter
1220289177Speter  return SVN_NO_ERROR;
1221289177Speter}
1222289177Speter
1223289177Speter
1224289177Speter/*** Public API implementations ***/
1225289177Speter
1226289177Spetersvn_error_t *
1227289177Spetersvn_fs_x__lock(svn_fs_t *fs,
1228289177Speter               apr_hash_t *targets,
1229289177Speter               const char *comment,
1230289177Speter               svn_boolean_t is_dav_comment,
1231289177Speter               apr_time_t expiration_date,
1232289177Speter               svn_boolean_t steal_lock,
1233289177Speter               svn_fs_lock_callback_t lock_callback,
1234289177Speter               void *lock_baton,
1235289177Speter               apr_pool_t *result_pool,
1236289177Speter               apr_pool_t *scratch_pool)
1237289177Speter{
1238289177Speter  lock_baton_t lb;
1239289177Speter  apr_array_header_t *sorted_targets;
1240289177Speter  apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1241289177Speter  apr_hash_index_t *hi;
1242289177Speter  apr_pool_t *iterpool;
1243289177Speter  svn_error_t *err, *cb_err = SVN_NO_ERROR;
1244289177Speter  int i;
1245289177Speter
1246289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1247289177Speter
1248289177Speter  /* We need to have a username attached to the fs. */
1249289177Speter  if (!fs->access_ctx || !fs->access_ctx->username)
1250289177Speter    return SVN_FS__ERR_NO_USER(fs);
1251289177Speter
1252289177Speter  /* The FS locking API allows both canonical and non-canonical
1253289177Speter     paths which means that the same canonical path could be
1254289177Speter     represented more than once in the TARGETS hash.  We just keep
1255289177Speter     one, choosing one with a token if possible. */
1256289177Speter  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1257289177Speter    {
1258289177Speter      const char *path = apr_hash_this_key(hi);
1259289177Speter      const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1260289177Speter      const svn_fs_lock_target_t *other;
1261289177Speter
1262289177Speter      path = svn_fspath__canonicalize(path, result_pool);
1263289177Speter      other = svn_hash_gets(canonical_targets, path);
1264289177Speter
1265289177Speter      if (!other || (!other->token && target->token))
1266289177Speter        svn_hash_sets(canonical_targets, path, target);
1267289177Speter    }
1268289177Speter
1269289177Speter  sorted_targets = svn_sort__hash(canonical_targets,
1270289177Speter                                  svn_sort_compare_items_as_paths,
1271289177Speter                                  scratch_pool);
1272289177Speter
1273289177Speter  lb.fs = fs;
1274289177Speter  lb.targets = sorted_targets;
1275289177Speter  lb.comment = comment;
1276289177Speter  lb.is_dav_comment = is_dav_comment;
1277289177Speter  lb.expiration_date = expiration_date;
1278289177Speter  lb.steal_lock = steal_lock;
1279289177Speter  lb.result_pool = result_pool;
1280289177Speter
1281289177Speter  iterpool = svn_pool_create(scratch_pool);
1282289177Speter  err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool);
1283289177Speter  for (i = 0; i < lb.infos->nelts; ++i)
1284289177Speter    {
1285289177Speter      struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1286289177Speter                                                struct lock_info_t);
1287289177Speter      svn_pool_clear(iterpool);
1288289177Speter      if (!cb_err && lock_callback)
1289289177Speter        {
1290289177Speter          if (!info->lock && !info->fs_err)
1291289177Speter            info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1292289177Speter                                             0, _("Failed to lock '%s'"),
1293289177Speter                                             info->path);
1294289177Speter
1295289177Speter          cb_err = lock_callback(lock_baton, info->path, info->lock,
1296289177Speter                                 info->fs_err, iterpool);
1297289177Speter        }
1298289177Speter      svn_error_clear(info->fs_err);
1299289177Speter    }
1300289177Speter  svn_pool_destroy(iterpool);
1301289177Speter
1302289177Speter  if (err && cb_err)
1303289177Speter    svn_error_compose(err, cb_err);
1304289177Speter  else if (!err)
1305289177Speter    err = cb_err;
1306289177Speter
1307289177Speter  return svn_error_trace(err);
1308289177Speter}
1309289177Speter
1310289177Speter
1311289177Spetersvn_error_t *
1312289177Spetersvn_fs_x__generate_lock_token(const char **token,
1313289177Speter                              svn_fs_t *fs,
1314289177Speter                              apr_pool_t *pool)
1315289177Speter{
1316289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1317289177Speter
1318289177Speter  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
1319289177Speter     want to use the fs UUID + some incremented number?  For now, we
1320289177Speter     generate a URI that matches the DAV RFC.  We could change this to
1321289177Speter     some other URI scheme someday, if we wish. */
1322289177Speter  *token = apr_pstrcat(pool, "opaquelocktoken:",
1323289177Speter                       svn_uuid_generate(pool), SVN_VA_NULL);
1324289177Speter  return SVN_NO_ERROR;
1325289177Speter}
1326289177Speter
1327289177Spetersvn_error_t *
1328289177Spetersvn_fs_x__unlock(svn_fs_t *fs,
1329289177Speter                 apr_hash_t *targets,
1330289177Speter                 svn_boolean_t break_lock,
1331289177Speter                 svn_fs_lock_callback_t lock_callback,
1332289177Speter                 void *lock_baton,
1333289177Speter                 apr_pool_t *result_pool,
1334289177Speter                 apr_pool_t *scratch_pool)
1335289177Speter{
1336289177Speter  unlock_baton_t ub;
1337289177Speter  apr_array_header_t *sorted_targets;
1338289177Speter  apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1339289177Speter  apr_hash_index_t *hi;
1340289177Speter  apr_pool_t *iterpool;
1341289177Speter  svn_error_t *err, *cb_err = SVN_NO_ERROR;
1342289177Speter  int i;
1343289177Speter
1344289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1345289177Speter
1346289177Speter  /* We need to have a username attached to the fs. */
1347289177Speter  if (!fs->access_ctx || !fs->access_ctx->username)
1348289177Speter    return SVN_FS__ERR_NO_USER(fs);
1349289177Speter
1350289177Speter  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1351289177Speter    {
1352289177Speter      const char *path = apr_hash_this_key(hi);
1353289177Speter      const char *token = apr_hash_this_val(hi);
1354289177Speter      const char *other;
1355289177Speter
1356289177Speter      path = svn_fspath__canonicalize(path, result_pool);
1357289177Speter      other = svn_hash_gets(canonical_targets, path);
1358289177Speter
1359289177Speter      if (!other)
1360289177Speter        svn_hash_sets(canonical_targets, path, token);
1361289177Speter    }
1362289177Speter
1363289177Speter  sorted_targets = svn_sort__hash(canonical_targets,
1364289177Speter                                  svn_sort_compare_items_as_paths,
1365289177Speter                                  scratch_pool);
1366289177Speter
1367289177Speter  ub.fs = fs;
1368289177Speter  ub.targets = sorted_targets;
1369289177Speter  ub.skip_check = FALSE;
1370289177Speter  ub.break_lock = break_lock;
1371289177Speter  ub.result_pool = result_pool;
1372289177Speter
1373289177Speter  iterpool = svn_pool_create(scratch_pool);
1374289177Speter  err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool);
1375289177Speter  for (i = 0; i < ub.infos->nelts; ++i)
1376289177Speter    {
1377289177Speter      unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t);
1378289177Speter      svn_pool_clear(iterpool);
1379289177Speter      if (!cb_err && lock_callback)
1380289177Speter        {
1381289177Speter          if (!info->done && !info->fs_err)
1382289177Speter            info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1383289177Speter                                             0, _("Failed to unlock '%s'"),
1384289177Speter                                             info->path);
1385289177Speter          cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1386289177Speter                                 iterpool);
1387289177Speter        }
1388289177Speter      svn_error_clear(info->fs_err);
1389289177Speter    }
1390289177Speter  svn_pool_destroy(iterpool);
1391289177Speter
1392289177Speter  if (err && cb_err)
1393289177Speter    svn_error_compose(err, cb_err);
1394289177Speter  else if (!err)
1395289177Speter    err = cb_err;
1396289177Speter
1397289177Speter  return svn_error_trace(err);
1398289177Speter}
1399289177Speter
1400289177Speter
1401289177Spetersvn_error_t *
1402289177Spetersvn_fs_x__get_lock(svn_lock_t **lock_p,
1403289177Speter                   svn_fs_t *fs,
1404289177Speter                   const char *path,
1405289177Speter                   apr_pool_t *pool)
1406289177Speter{
1407289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1408289177Speter  path = svn_fs__canonicalize_abspath(path, pool);
1409289177Speter  return get_lock_helper(fs, lock_p, path, FALSE, pool);
1410289177Speter}
1411289177Speter
1412289177Speter
1413289177Speter/* Baton for get_locks_filter_func(). */
1414289177Spetertypedef struct get_locks_filter_baton_t
1415289177Speter{
1416289177Speter  const char *path;
1417289177Speter  svn_depth_t requested_depth;
1418289177Speter  svn_fs_get_locks_callback_t get_locks_func;
1419289177Speter  void *get_locks_baton;
1420289177Speter
1421289177Speter} get_locks_filter_baton_t;
1422289177Speter
1423289177Speter
1424289177Speter/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks()
1425289177Speter   which filters out locks on paths that aren't within
1426289177Speter   BATON->requested_depth of BATON->path before called
1427289177Speter   BATON->get_locks_func() with BATON->get_locks_baton.
1428289177Speter
1429289177Speter   NOTE: See issue #3660 for details about how the FSX lock
1430289177Speter   management code is inconsistent.  Until that inconsistency is
1431289177Speter   resolved, we take this filtering approach rather than honoring
1432289177Speter   depth requests closer to the crawling code.  In other words, once
1433289177Speter   we decide how to resolve issue #3660, there might be a more
1434289177Speter   performant way to honor the depth passed to svn_fs_x__get_locks().  */
1435289177Speterstatic svn_error_t *
1436289177Speterget_locks_filter_func(void *baton,
1437289177Speter                      svn_lock_t *lock,
1438289177Speter                      apr_pool_t *pool)
1439289177Speter{
1440289177Speter  get_locks_filter_baton_t *b = baton;
1441289177Speter
1442289177Speter  /* Filter out unwanted paths.  Since Subversion only allows
1443289177Speter     locks on files, we can treat depth=immediates the same as
1444289177Speter     depth=files for filtering purposes.  Meaning, we'll keep
1445289177Speter     this lock if:
1446289177Speter
1447289177Speter     a) its path is the very path we queried, or
1448289177Speter     b) we've asked for a fully recursive answer, or
1449289177Speter     c) we've asked for depth=files or depth=immediates, and this
1450289177Speter        lock is on an immediate child of our query path.
1451289177Speter  */
1452289177Speter  if ((strcmp(b->path, lock->path) == 0)
1453289177Speter      || (b->requested_depth == svn_depth_infinity))
1454289177Speter    {
1455289177Speter      SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1456289177Speter    }
1457289177Speter  else if ((b->requested_depth == svn_depth_files) ||
1458289177Speter           (b->requested_depth == svn_depth_immediates))
1459289177Speter    {
1460289177Speter      const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1461289177Speter      if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1462289177Speter        SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1463289177Speter    }
1464289177Speter
1465289177Speter  return SVN_NO_ERROR;
1466289177Speter}
1467289177Speter
1468289177Spetersvn_error_t *
1469289177Spetersvn_fs_x__get_locks(svn_fs_t *fs,
1470289177Speter                    const char *path,
1471289177Speter                    svn_depth_t depth,
1472289177Speter                    svn_fs_get_locks_callback_t get_locks_func,
1473289177Speter                    void *get_locks_baton,
1474289177Speter                    apr_pool_t *scratch_pool)
1475289177Speter{
1476289177Speter  const char *digest_path;
1477289177Speter  get_locks_filter_baton_t glfb;
1478289177Speter
1479289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1480289177Speter  path = svn_fs__canonicalize_abspath(path, scratch_pool);
1481289177Speter
1482289177Speter  glfb.path = path;
1483289177Speter  glfb.requested_depth = depth;
1484289177Speter  glfb.get_locks_func = get_locks_func;
1485289177Speter  glfb.get_locks_baton = get_locks_baton;
1486289177Speter
1487289177Speter  /* Get the top digest path in our tree of interest, and then walk it. */
1488289177Speter  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool));
1489289177Speter  SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1490289177Speter                     FALSE, scratch_pool));
1491289177Speter  return SVN_NO_ERROR;
1492289177Speter}
1493