1251881Speter/* lock.c :  functions for manipulating filesystem locks.
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter
24251881Speter#include "svn_pools.h"
25251881Speter#include "svn_error.h"
26251881Speter#include "svn_dirent_uri.h"
27251881Speter#include "svn_path.h"
28251881Speter#include "svn_fs.h"
29251881Speter#include "svn_hash.h"
30251881Speter#include "svn_time.h"
31251881Speter#include "svn_utf.h"
32251881Speter
33251881Speter#include <apr_uuid.h>
34251881Speter#include <apr_file_io.h>
35251881Speter#include <apr_file_info.h>
36251881Speter
37251881Speter#include "lock.h"
38251881Speter#include "tree.h"
39251881Speter#include "fs_fs.h"
40251881Speter#include "../libsvn_fs/fs-loader.h"
41251881Speter
42251881Speter#include "private/svn_fs_util.h"
43251881Speter#include "private/svn_fspath.h"
44251881Speter#include "svn_private_config.h"
45251881Speter
46251881Speter/* Names of hash keys used to store a lock for writing to disk. */
47251881Speter#define PATH_KEY "path"
48251881Speter#define TOKEN_KEY "token"
49251881Speter#define OWNER_KEY "owner"
50251881Speter#define CREATION_DATE_KEY "creation_date"
51251881Speter#define EXPIRATION_DATE_KEY "expiration_date"
52251881Speter#define COMMENT_KEY "comment"
53251881Speter#define IS_DAV_COMMENT_KEY "is_dav_comment"
54251881Speter#define CHILDREN_KEY "children"
55251881Speter
56251881Speter/* Number of characters from the head of a digest file name used to
57251881Speter   calculate a subdirectory in which to drop that file. */
58251881Speter#define DIGEST_SUBDIR_LEN 3
59251881Speter
60251881Speter
61251881Speter
62251881Speter/*** Generic helper functions. ***/
63251881Speter
64251881Speter/* Set *DIGEST to the MD5 hash of STR. */
65251881Speterstatic svn_error_t *
66251881Spetermake_digest(const char **digest,
67251881Speter            const char *str,
68251881Speter            apr_pool_t *pool)
69251881Speter{
70251881Speter  svn_checksum_t *checksum;
71251881Speter
72251881Speter  SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
73251881Speter
74251881Speter  *digest = svn_checksum_to_cstring_display(checksum, pool);
75251881Speter  return SVN_NO_ERROR;
76251881Speter}
77251881Speter
78251881Speter
79251881Speter/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
80251881Speter   if unknown) to an svn_string_t-ized version of VALUE (whose size is
81251881Speter   VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
82251881Speter   will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
83251881Speter   is NULL, this function will do nothing. */
84251881Speterstatic void
85251881Speterhash_store(apr_hash_t *hash,
86251881Speter           const char *key,
87251881Speter           apr_ssize_t key_len,
88251881Speter           const char *value,
89251881Speter           apr_ssize_t value_len,
90251881Speter           apr_pool_t *pool)
91251881Speter{
92251881Speter  if (! (key && value))
93251881Speter    return;
94251881Speter  if (value_len == APR_HASH_KEY_STRING)
95251881Speter    value_len = strlen(value);
96251881Speter  apr_hash_set(hash, key, key_len,
97251881Speter               svn_string_ncreate(value, value_len, pool));
98251881Speter}
99251881Speter
100251881Speter
101251881Speter/* Fetch the value of KEY from HASH, returning only the cstring data
102251881Speter   of that value (if it exists). */
103251881Speterstatic const char *
104251881Speterhash_fetch(apr_hash_t *hash,
105251881Speter           const char *key,
106251881Speter           apr_pool_t *pool)
107251881Speter{
108251881Speter  svn_string_t *str = svn_hash_gets(hash, key);
109251881Speter  return str ? str->data : NULL;
110251881Speter}
111251881Speter
112251881Speter
113251881Speter/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt.  */
114251881Speterstatic svn_error_t *
115251881Spetererr_corrupt_lockfile(const char *fs_path, const char *path)
116251881Speter{
117251881Speter  return
118251881Speter    svn_error_createf(
119251881Speter     SVN_ERR_FS_CORRUPT, 0,
120251881Speter     _("Corrupt lockfile for path '%s' in filesystem '%s'"),
121251881Speter     path, fs_path);
122251881Speter}
123251881Speter
124251881Speter
125251881Speter/*** Digest file handling functions. ***/
126251881Speter
127251881Speter/* Return the path of the lock/entries file for which DIGEST is the
128251881Speter   hashed repository relative path. */
129251881Speterstatic const char *
130251881Speterdigest_path_from_digest(const char *fs_path,
131251881Speter                        const char *digest,
132251881Speter                        apr_pool_t *pool)
133251881Speter{
134251881Speter  return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
135251881Speter                              apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
136251881Speter                              digest, NULL);
137251881Speter}
138251881Speter
139251881Speter
140251881Speter/* Set *DIGEST_PATH to the path to the lock/entries digest file associate
141251881Speter   with PATH, where PATH is the path to the lock file or lock entries file
142251881Speter   in FS. */
143251881Speterstatic svn_error_t *
144251881Speterdigest_path_from_path(const char **digest_path,
145251881Speter                      const char *fs_path,
146251881Speter                      const char *path,
147251881Speter                      apr_pool_t *pool)
148251881Speter{
149251881Speter  const char *digest;
150251881Speter  SVN_ERR(make_digest(&digest, path, pool));
151251881Speter  *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
152251881Speter                                      apr_pstrmemdup(pool, digest,
153251881Speter                                                     DIGEST_SUBDIR_LEN),
154251881Speter                                      digest, NULL);
155251881Speter  return SVN_NO_ERROR;
156251881Speter}
157251881Speter
158251881Speter
159251881Speter/* Write to DIGEST_PATH a representation of CHILDREN (which may be
160251881Speter   empty, if the versioned path in FS represented by DIGEST_PATH has
161251881Speter   no children) and LOCK (which may be NULL if that versioned path is
162251881Speter   lock itself locked).  Set the permissions of DIGEST_PATH to those of
163251881Speter   PERMS_REFERENCE.  Use POOL for all allocations.
164251881Speter */
165251881Speterstatic svn_error_t *
166251881Speterwrite_digest_file(apr_hash_t *children,
167251881Speter                  svn_lock_t *lock,
168251881Speter                  const char *fs_path,
169251881Speter                  const char *digest_path,
170251881Speter                  const char *perms_reference,
171251881Speter                  apr_pool_t *pool)
172251881Speter{
173251881Speter  svn_error_t *err = SVN_NO_ERROR;
174251881Speter  svn_stream_t *stream;
175251881Speter  apr_hash_index_t *hi;
176251881Speter  apr_hash_t *hash = apr_hash_make(pool);
177251881Speter  const char *tmp_path;
178251881Speter
179251881Speter  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
180251881Speter                                                       pool), fs_path, pool));
181251881Speter  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
182251881Speter                                       fs_path, pool));
183251881Speter
184251881Speter  if (lock)
185251881Speter    {
186251881Speter      const char *creation_date = NULL, *expiration_date = NULL;
187251881Speter      if (lock->creation_date)
188251881Speter        creation_date = svn_time_to_cstring(lock->creation_date, pool);
189251881Speter      if (lock->expiration_date)
190251881Speter        expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
191251881Speter      hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
192251881Speter                 lock->path, APR_HASH_KEY_STRING, pool);
193251881Speter      hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
194251881Speter                 lock->token, APR_HASH_KEY_STRING, pool);
195251881Speter      hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
196251881Speter                 lock->owner, APR_HASH_KEY_STRING, pool);
197251881Speter      hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
198251881Speter                 lock->comment, APR_HASH_KEY_STRING, pool);
199251881Speter      hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
200251881Speter                 lock->is_dav_comment ? "1" : "0", 1, pool);
201251881Speter      hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
202251881Speter                 creation_date, APR_HASH_KEY_STRING, pool);
203251881Speter      hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
204251881Speter                 expiration_date, APR_HASH_KEY_STRING, pool);
205251881Speter    }
206251881Speter  if (apr_hash_count(children))
207251881Speter    {
208251881Speter      svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool);
209251881Speter      for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
210251881Speter        {
211251881Speter          svn_stringbuf_appendbytes(children_list,
212251881Speter                                    svn__apr_hash_index_key(hi),
213251881Speter                                    svn__apr_hash_index_klen(hi));
214251881Speter          svn_stringbuf_appendbyte(children_list, '\n');
215251881Speter        }
216251881Speter      hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
217251881Speter                 children_list->data, children_list->len, pool);
218251881Speter    }
219251881Speter
220251881Speter  SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
221251881Speter                                 svn_dirent_dirname(digest_path, pool),
222251881Speter                                 svn_io_file_del_none, pool, pool));
223251881Speter  if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
224251881Speter    {
225251881Speter      svn_error_clear(svn_stream_close(stream));
226251881Speter      return svn_error_createf(err->apr_err,
227251881Speter                               err,
228251881Speter                               _("Cannot write lock/entries hashfile '%s'"),
229251881Speter                               svn_dirent_local_style(tmp_path, pool));
230251881Speter    }
231251881Speter
232251881Speter  SVN_ERR(svn_stream_close(stream));
233251881Speter  SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
234251881Speter  SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
235251881Speter  return SVN_NO_ERROR;
236251881Speter}
237251881Speter
238251881Speter
239251881Speter/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
240251881Speter   file (if it exists, and if *LOCK_P is non-NULL) and the hash of
241251881Speter   CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL).  Use POOL
242251881Speter   for all allocations.  */
243251881Speterstatic svn_error_t *
244251881Speterread_digest_file(apr_hash_t **children_p,
245251881Speter                 svn_lock_t **lock_p,
246251881Speter                 const char *fs_path,
247251881Speter                 const char *digest_path,
248251881Speter                 apr_pool_t *pool)
249251881Speter{
250251881Speter  svn_error_t *err = SVN_NO_ERROR;
251251881Speter  svn_lock_t *lock;
252251881Speter  apr_hash_t *hash;
253251881Speter  svn_stream_t *stream;
254251881Speter  const char *val;
255251881Speter
256251881Speter  if (lock_p)
257251881Speter    *lock_p = NULL;
258251881Speter  if (children_p)
259251881Speter    *children_p = apr_hash_make(pool);
260251881Speter
261251881Speter  err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
262251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
263251881Speter    {
264251881Speter      svn_error_clear(err);
265251881Speter      return SVN_NO_ERROR;
266251881Speter    }
267251881Speter  SVN_ERR(err);
268251881Speter
269251881Speter  /* If our caller doesn't care about anything but the presence of the
270251881Speter     file... whatever. */
271251881Speter  if (! (lock_p || children_p))
272251881Speter    return svn_stream_close(stream);
273251881Speter
274251881Speter  hash = apr_hash_make(pool);
275251881Speter  if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
276251881Speter    {
277251881Speter      svn_error_clear(svn_stream_close(stream));
278251881Speter      return svn_error_createf(err->apr_err,
279251881Speter                               err,
280251881Speter                               _("Can't parse lock/entries hashfile '%s'"),
281251881Speter                               svn_dirent_local_style(digest_path, pool));
282251881Speter    }
283251881Speter  SVN_ERR(svn_stream_close(stream));
284251881Speter
285251881Speter  /* If our caller cares, see if we have a lock path in our hash. If
286251881Speter     so, we'll assume we have a lock here. */
287251881Speter  val = hash_fetch(hash, PATH_KEY, pool);
288251881Speter  if (val && lock_p)
289251881Speter    {
290251881Speter      const char *path = val;
291251881Speter
292251881Speter      /* Create our lock and load it up. */
293251881Speter      lock = svn_lock_create(pool);
294251881Speter      lock->path = path;
295251881Speter
296251881Speter      if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
297251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
298251881Speter
299251881Speter      if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
300251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
301251881Speter
302251881Speter      if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
303251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
304251881Speter      lock->is_dav_comment = (val[0] == '1');
305251881Speter
306251881Speter      if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
307251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
308251881Speter      SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
309251881Speter
310251881Speter      if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
311251881Speter        SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
312251881Speter
313251881Speter      lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
314251881Speter
315251881Speter      *lock_p = lock;
316251881Speter    }
317251881Speter
318251881Speter  /* If our caller cares, see if we have any children for this path. */
319251881Speter  val = hash_fetch(hash, CHILDREN_KEY, pool);
320251881Speter  if (val && children_p)
321251881Speter    {
322251881Speter      apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
323251881Speter      int i;
324251881Speter
325251881Speter      for (i = 0; i < kiddos->nelts; i++)
326251881Speter        {
327251881Speter          svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
328251881Speter                        (void *)1);
329251881Speter        }
330251881Speter    }
331251881Speter  return SVN_NO_ERROR;
332251881Speter}
333251881Speter
334251881Speter
335251881Speter
336251881Speter/*** Lock helper functions (path here are still FS paths, not on-disk
337251881Speter     schema-supporting paths) ***/
338251881Speter
339251881Speter
340251881Speter/* Write LOCK in FS to the actual OS filesystem.
341251881Speter
342251881Speter   Use PERMS_REFERENCE for the permissions of any digest files.
343251881Speter
344251881Speter   Note: this takes an FS_PATH because it's called from the hotcopy logic.
345251881Speter */
346251881Speterstatic svn_error_t *
347251881Speterset_lock(const char *fs_path,
348251881Speter         svn_lock_t *lock,
349251881Speter         const char *perms_reference,
350251881Speter         apr_pool_t *pool)
351251881Speter{
352251881Speter  svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
353251881Speter  const char *lock_digest_path = NULL;
354251881Speter  apr_pool_t *subpool;
355251881Speter
356251881Speter  SVN_ERR_ASSERT(lock);
357251881Speter
358251881Speter  /* Iterate in reverse, creating the lock for LOCK->path, and then
359251881Speter     just adding entries for its parent, until we reach a parent
360251881Speter     that's already listed in *its* parent. */
361251881Speter  subpool = svn_pool_create(pool);
362251881Speter  while (1729)
363251881Speter    {
364251881Speter      const char *digest_path, *digest_file;
365251881Speter      apr_hash_t *this_children;
366251881Speter      svn_lock_t *this_lock;
367251881Speter
368251881Speter      svn_pool_clear(subpool);
369251881Speter
370251881Speter      /* Calculate the DIGEST_PATH for the currently FS path, and then
371251881Speter         get its DIGEST_FILE basename. */
372251881Speter      SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
373251881Speter                                    subpool));
374251881Speter      digest_file = svn_dirent_basename(digest_path, subpool);
375251881Speter
376251881Speter      SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
377251881Speter                               digest_path, subpool));
378251881Speter
379251881Speter      /* We're either writing a new lock (first time through only) or
380251881Speter         a new entry (every time but the first). */
381251881Speter      if (lock)
382251881Speter        {
383251881Speter          this_lock = lock;
384251881Speter          lock = NULL;
385251881Speter          lock_digest_path = apr_pstrdup(pool, digest_file);
386251881Speter        }
387251881Speter      else
388251881Speter        {
389251881Speter          /* If we already have an entry for this path, we're done. */
390251881Speter          if (svn_hash_gets(this_children, lock_digest_path))
391251881Speter            break;
392251881Speter          svn_hash_sets(this_children, lock_digest_path, (void *)1);
393251881Speter        }
394251881Speter      SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
395251881Speter                                digest_path, perms_reference, subpool));
396251881Speter
397251881Speter      /* Prep for next iteration, or bail if we're done. */
398251881Speter      if (svn_fspath__is_root(this_path->data, this_path->len))
399251881Speter        break;
400251881Speter      svn_stringbuf_set(this_path,
401251881Speter                        svn_fspath__dirname(this_path->data, subpool));
402251881Speter    }
403251881Speter
404251881Speter  svn_pool_destroy(subpool);
405251881Speter  return SVN_NO_ERROR;
406251881Speter}
407251881Speter
408251881Speter/* Delete LOCK from FS in the actual OS filesystem. */
409251881Speterstatic svn_error_t *
410251881Speterdelete_lock(svn_fs_t *fs,
411251881Speter            svn_lock_t *lock,
412251881Speter            apr_pool_t *pool)
413251881Speter{
414251881Speter  svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
415251881Speter  const char *child_to_kill = NULL;
416251881Speter  apr_pool_t *subpool;
417251881Speter
418251881Speter  SVN_ERR_ASSERT(lock);
419251881Speter
420251881Speter  /* Iterate in reverse, deleting the lock for LOCK->path, and then
421251881Speter     deleting its entry as it appears in each of its parents. */
422251881Speter  subpool = svn_pool_create(pool);
423251881Speter  while (1729)
424251881Speter    {
425251881Speter      const char *digest_path, *digest_file;
426251881Speter      apr_hash_t *this_children;
427251881Speter      svn_lock_t *this_lock;
428251881Speter
429251881Speter      svn_pool_clear(subpool);
430251881Speter
431251881Speter      /* Calculate the DIGEST_PATH for the currently FS path, and then
432251881Speter         get its DIGEST_FILE basename. */
433251881Speter      SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
434251881Speter                                    subpool));
435251881Speter      digest_file = svn_dirent_basename(digest_path, subpool);
436251881Speter
437251881Speter      SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
438251881Speter                               digest_path, subpool));
439251881Speter
440251881Speter      /* Delete the lock (first time through only). */
441251881Speter      if (lock)
442251881Speter        {
443251881Speter          this_lock = NULL;
444251881Speter          lock = NULL;
445251881Speter          child_to_kill = apr_pstrdup(pool, digest_file);
446251881Speter        }
447251881Speter
448251881Speter      if (child_to_kill)
449251881Speter        svn_hash_sets(this_children, child_to_kill, NULL);
450251881Speter
451251881Speter      if (! (this_lock || apr_hash_count(this_children) != 0))
452251881Speter        {
453251881Speter          /* Special case:  no goodz, no file.  And remember to nix
454251881Speter             the entry for it in its parent. */
455251881Speter          SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
456251881Speter        }
457251881Speter      else
458251881Speter        {
459251881Speter          const char *rev_0_path;
460251881Speter          SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
461251881Speter          SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
462251881Speter                                    digest_path, rev_0_path, subpool));
463251881Speter        }
464251881Speter
465251881Speter      /* Prep for next iteration, or bail if we're done. */
466251881Speter      if (svn_fspath__is_root(this_path->data, this_path->len))
467251881Speter        break;
468251881Speter      svn_stringbuf_set(this_path,
469251881Speter                        svn_fspath__dirname(this_path->data, subpool));
470251881Speter    }
471251881Speter
472251881Speter  svn_pool_destroy(subpool);
473251881Speter  return SVN_NO_ERROR;
474251881Speter}
475251881Speter
476251881Speter/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
477251881Speter   TRUE if the caller (or one of its callers) has taken out the
478251881Speter   repository-wide write lock, FALSE otherwise.  If MUST_EXIST is
479251881Speter   not set, the function will simply return NULL in *LOCK_P instead
480251881Speter   of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
481251881Speter   was not found (much faster).  Use POOL for allocations. */
482251881Speterstatic svn_error_t *
483251881Speterget_lock(svn_lock_t **lock_p,
484251881Speter         svn_fs_t *fs,
485251881Speter         const char *path,
486251881Speter         svn_boolean_t have_write_lock,
487251881Speter         svn_boolean_t must_exist,
488251881Speter         apr_pool_t *pool)
489251881Speter{
490251881Speter  svn_lock_t *lock = NULL;
491251881Speter  const char *digest_path;
492251881Speter  svn_node_kind_t kind;
493251881Speter
494251881Speter  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
495251881Speter  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
496251881Speter
497251881Speter  *lock_p = NULL;
498251881Speter  if (kind != svn_node_none)
499251881Speter    SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
500251881Speter
501251881Speter  if (! lock)
502251881Speter    return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
503251881Speter
504251881Speter  /* Don't return an expired lock. */
505251881Speter  if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
506251881Speter    {
507251881Speter      /* Only remove the lock if we have the write lock.
508251881Speter         Read operations shouldn't change the filesystem. */
509251881Speter      if (have_write_lock)
510251881Speter        SVN_ERR(delete_lock(fs, lock, pool));
511251881Speter      return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
512251881Speter    }
513251881Speter
514251881Speter  *lock_p = lock;
515251881Speter  return SVN_NO_ERROR;
516251881Speter}
517251881Speter
518251881Speter
519251881Speter/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
520251881Speter   TRUE if the caller (or one of its callers) has taken out the
521251881Speter   repository-wide write lock, FALSE otherwise.  Use POOL for
522251881Speter   allocations. */
523251881Speterstatic svn_error_t *
524251881Speterget_lock_helper(svn_fs_t *fs,
525251881Speter                svn_lock_t **lock_p,
526251881Speter                const char *path,
527251881Speter                svn_boolean_t have_write_lock,
528251881Speter                apr_pool_t *pool)
529251881Speter{
530251881Speter  svn_lock_t *lock;
531251881Speter  svn_error_t *err;
532251881Speter
533251881Speter  err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
534251881Speter
535251881Speter  /* We've deliberately decided that this function doesn't tell the
536251881Speter     caller *why* the lock is unavailable.  */
537251881Speter  if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
538251881Speter              || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
539251881Speter    {
540251881Speter      svn_error_clear(err);
541251881Speter      *lock_p = NULL;
542251881Speter      return SVN_NO_ERROR;
543251881Speter    }
544251881Speter  else
545251881Speter    SVN_ERR(err);
546251881Speter
547251881Speter  *lock_p = lock;
548251881Speter  return SVN_NO_ERROR;
549251881Speter}
550251881Speter
551251881Speter
552251881Speter/* Baton for locks_walker(). */
553251881Speterstruct walk_locks_baton {
554251881Speter  svn_fs_get_locks_callback_t get_locks_func;
555251881Speter  void *get_locks_baton;
556251881Speter  svn_fs_t *fs;
557251881Speter};
558251881Speter
559251881Speter/* Implements walk_digests_callback_t. */
560251881Speterstatic svn_error_t *
561251881Speterlocks_walker(void *baton,
562251881Speter             const char *fs_path,
563251881Speter             const char *digest_path,
564251881Speter             apr_hash_t *children,
565251881Speter             svn_lock_t *lock,
566251881Speter             svn_boolean_t have_write_lock,
567251881Speter             apr_pool_t *pool)
568251881Speter{
569251881Speter  struct walk_locks_baton *wlb = baton;
570251881Speter
571251881Speter  if (lock)
572251881Speter    {
573251881Speter      /* Don't report an expired lock. */
574251881Speter      if (lock->expiration_date == 0
575251881Speter          || (apr_time_now() <= lock->expiration_date))
576251881Speter        {
577251881Speter          if (wlb->get_locks_func)
578251881Speter            SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
579251881Speter        }
580251881Speter      else
581251881Speter        {
582251881Speter          /* Only remove the lock if we have the write lock.
583251881Speter             Read operations shouldn't change the filesystem. */
584251881Speter          if (have_write_lock)
585251881Speter            SVN_ERR(delete_lock(wlb->fs, lock, pool));
586251881Speter        }
587251881Speter    }
588251881Speter
589251881Speter  return SVN_NO_ERROR;
590251881Speter}
591251881Speter
592251881Speter/* Callback type for walk_digest_files().
593251881Speter *
594251881Speter * CHILDREN and LOCK come from a read_digest_file(digest_path) call.
595251881Speter */
596251881Spetertypedef svn_error_t *(*walk_digests_callback_t)(void *baton,
597251881Speter                                                const char *fs_path,
598251881Speter                                                const char *digest_path,
599251881Speter                                                apr_hash_t *children,
600251881Speter                                                svn_lock_t *lock,
601251881Speter                                                svn_boolean_t have_write_lock,
602251881Speter                                                apr_pool_t *pool);
603251881Speter
604251881Speter/* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
605251881Speter   all lock digest files in and under PATH in FS.
606251881Speter   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
607251881Speter   has the FS write lock. */
608251881Speterstatic svn_error_t *
609251881Speterwalk_digest_files(const char *fs_path,
610251881Speter                  const char *digest_path,
611251881Speter                  walk_digests_callback_t walk_digests_func,
612251881Speter                  void *walk_digests_baton,
613251881Speter                  svn_boolean_t have_write_lock,
614251881Speter                  apr_pool_t *pool)
615251881Speter{
616251881Speter  apr_hash_index_t *hi;
617251881Speter  apr_hash_t *children;
618251881Speter  apr_pool_t *subpool;
619251881Speter  svn_lock_t *lock;
620251881Speter
621251881Speter  /* First, send up any locks in the current digest file. */
622251881Speter  SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
623251881Speter
624251881Speter  SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
625251881Speter                            children, lock,
626251881Speter                            have_write_lock, pool));
627251881Speter
628251881Speter  /* Now, recurse on this thing's child entries (if any; bail otherwise). */
629251881Speter  if (! apr_hash_count(children))
630251881Speter    return SVN_NO_ERROR;
631251881Speter  subpool = svn_pool_create(pool);
632251881Speter  for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
633251881Speter    {
634251881Speter      const char *digest = svn__apr_hash_index_key(hi);
635251881Speter      svn_pool_clear(subpool);
636251881Speter      SVN_ERR(walk_digest_files
637251881Speter              (fs_path, digest_path_from_digest(fs_path, digest, subpool),
638251881Speter               walk_digests_func, walk_digests_baton, have_write_lock, subpool));
639251881Speter    }
640251881Speter  svn_pool_destroy(subpool);
641251881Speter  return SVN_NO_ERROR;
642251881Speter}
643251881Speter
644251881Speter/* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
645251881Speter   all locks in and under PATH in FS.
646251881Speter   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
647251881Speter   has the FS write lock. */
648251881Speterstatic svn_error_t *
649251881Speterwalk_locks(svn_fs_t *fs,
650251881Speter           const char *digest_path,
651251881Speter           svn_fs_get_locks_callback_t get_locks_func,
652251881Speter           void *get_locks_baton,
653251881Speter           svn_boolean_t have_write_lock,
654251881Speter           apr_pool_t *pool)
655251881Speter{
656251881Speter  struct walk_locks_baton wlb;
657251881Speter
658251881Speter  wlb.get_locks_func = get_locks_func;
659251881Speter  wlb.get_locks_baton = get_locks_baton;
660251881Speter  wlb.fs = fs;
661251881Speter  SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
662251881Speter                            have_write_lock, pool));
663251881Speter  return SVN_NO_ERROR;
664251881Speter}
665251881Speter
666251881Speter
667251881Speter/* Utility function:  verify that a lock can be used.  Interesting
668251881Speter   errors returned from this function:
669251881Speter
670251881Speter      SVN_ERR_FS_NO_USER: No username attached to FS.
671251881Speter      SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
672251881Speter      SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
673251881Speter */
674251881Speterstatic svn_error_t *
675251881Speterverify_lock(svn_fs_t *fs,
676251881Speter            svn_lock_t *lock,
677251881Speter            apr_pool_t *pool)
678251881Speter{
679251881Speter  if ((! fs->access_ctx) || (! fs->access_ctx->username))
680251881Speter    return svn_error_createf
681251881Speter      (SVN_ERR_FS_NO_USER, NULL,
682251881Speter       _("Cannot verify lock on path '%s'; no username available"),
683251881Speter       lock->path);
684251881Speter
685251881Speter  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
686251881Speter    return svn_error_createf
687251881Speter      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
688251881Speter       _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
689251881Speter       fs->access_ctx->username, lock->path, lock->owner);
690251881Speter
691251881Speter  else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
692251881Speter    return svn_error_createf
693251881Speter      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
694251881Speter       _("Cannot verify lock on path '%s'; no matching lock-token available"),
695251881Speter       lock->path);
696251881Speter
697251881Speter  return SVN_NO_ERROR;
698251881Speter}
699251881Speter
700251881Speter
701251881Speter/* This implements the svn_fs_get_locks_callback_t interface, where
702251881Speter   BATON is just an svn_fs_t object. */
703251881Speterstatic svn_error_t *
704251881Speterget_locks_callback(void *baton,
705251881Speter                   svn_lock_t *lock,
706251881Speter                   apr_pool_t *pool)
707251881Speter{
708251881Speter  return verify_lock(baton, lock, pool);
709251881Speter}
710251881Speter
711251881Speter
712251881Speter/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
713251881Spetersvn_error_t *
714251881Spetersvn_fs_fs__allow_locked_operation(const char *path,
715251881Speter                                  svn_fs_t *fs,
716251881Speter                                  svn_boolean_t recurse,
717251881Speter                                  svn_boolean_t have_write_lock,
718251881Speter                                  apr_pool_t *pool)
719251881Speter{
720251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
721251881Speter  if (recurse)
722251881Speter    {
723251881Speter      /* Discover all locks at or below the path. */
724251881Speter      const char *digest_path;
725251881Speter      SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
726251881Speter      SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
727251881Speter                         fs, have_write_lock, pool));
728251881Speter    }
729251881Speter  else
730251881Speter    {
731251881Speter      /* Discover and verify any lock attached to the path. */
732251881Speter      svn_lock_t *lock;
733251881Speter      SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
734251881Speter      if (lock)
735251881Speter        SVN_ERR(verify_lock(fs, lock, pool));
736251881Speter    }
737251881Speter  return SVN_NO_ERROR;
738251881Speter}
739251881Speter
740251881Speter/* Baton used for lock_body below. */
741251881Speterstruct lock_baton {
742251881Speter  svn_lock_t **lock_p;
743251881Speter  svn_fs_t *fs;
744251881Speter  const char *path;
745251881Speter  const char *token;
746251881Speter  const char *comment;
747251881Speter  svn_boolean_t is_dav_comment;
748251881Speter  apr_time_t expiration_date;
749251881Speter  svn_revnum_t current_rev;
750251881Speter  svn_boolean_t steal_lock;
751251881Speter  apr_pool_t *pool;
752251881Speter};
753251881Speter
754251881Speter
755251881Speter/* This implements the svn_fs_fs__with_write_lock() 'body' callback
756251881Speter   type, and assumes that the write lock is held.
757251881Speter   BATON is a 'struct lock_baton *'. */
758251881Speterstatic svn_error_t *
759251881Speterlock_body(void *baton, apr_pool_t *pool)
760251881Speter{
761251881Speter  struct lock_baton *lb = baton;
762251881Speter  svn_node_kind_t kind;
763251881Speter  svn_lock_t *existing_lock;
764251881Speter  svn_lock_t *lock;
765251881Speter  svn_fs_root_t *root;
766251881Speter  svn_revnum_t youngest;
767251881Speter  const char *rev_0_path;
768251881Speter
769251881Speter  /* Until we implement directory locks someday, we only allow locks
770251881Speter     on files or non-existent paths. */
771251881Speter  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
772251881Speter     library dependencies, which are not portable. */
773251881Speter  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
774251881Speter  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
775251881Speter  SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
776251881Speter  if (kind == svn_node_dir)
777251881Speter    return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
778251881Speter
779251881Speter  /* While our locking implementation easily supports the locking of
780251881Speter     nonexistent paths, we deliberately choose not to allow such madness. */
781251881Speter  if (kind == svn_node_none)
782251881Speter    {
783251881Speter      if (SVN_IS_VALID_REVNUM(lb->current_rev))
784251881Speter        return svn_error_createf(
785251881Speter          SVN_ERR_FS_OUT_OF_DATE, NULL,
786251881Speter          _("Path '%s' doesn't exist in HEAD revision"),
787251881Speter          lb->path);
788251881Speter      else
789251881Speter        return svn_error_createf(
790251881Speter          SVN_ERR_FS_NOT_FOUND, NULL,
791251881Speter          _("Path '%s' doesn't exist in HEAD revision"),
792251881Speter          lb->path);
793251881Speter    }
794251881Speter
795251881Speter  /* We need to have a username attached to the fs. */
796251881Speter  if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
797251881Speter    return SVN_FS__ERR_NO_USER(lb->fs);
798251881Speter
799251881Speter  /* Is the caller attempting to lock an out-of-date working file? */
800251881Speter  if (SVN_IS_VALID_REVNUM(lb->current_rev))
801251881Speter    {
802251881Speter      svn_revnum_t created_rev;
803251881Speter      SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
804251881Speter                                          pool));
805251881Speter
806251881Speter      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
807251881Speter         apparently somebody is trying to lock something in their
808251881Speter         working copy, but somebody else has deleted the thing
809251881Speter         from HEAD.  That counts as being 'out of date'. */
810251881Speter      if (! SVN_IS_VALID_REVNUM(created_rev))
811251881Speter        return svn_error_createf
812251881Speter          (SVN_ERR_FS_OUT_OF_DATE, NULL,
813251881Speter           _("Path '%s' doesn't exist in HEAD revision"), lb->path);
814251881Speter
815251881Speter      if (lb->current_rev < created_rev)
816251881Speter        return svn_error_createf
817251881Speter          (SVN_ERR_FS_OUT_OF_DATE, NULL,
818251881Speter           _("Lock failed: newer version of '%s' exists"), lb->path);
819251881Speter    }
820251881Speter
821251881Speter  /* If the caller provided a TOKEN, we *really* need to see
822251881Speter     if a lock already exists with that token, and if so, verify that
823251881Speter     the lock's path matches PATH.  Otherwise we run the risk of
824251881Speter     breaking the 1-to-1 mapping of lock tokens to locked paths. */
825251881Speter  /* ### TODO:  actually do this check.  This is tough, because the
826251881Speter     schema doesn't supply a lookup-by-token mechanism. */
827251881Speter
828251881Speter  /* Is the path already locked?
829251881Speter
830251881Speter     Note that this next function call will automatically ignore any
831251881Speter     errors about {the path not existing as a key, the path's token
832251881Speter     not existing as a key, the lock just having been expired}.  And
833251881Speter     that's totally fine.  Any of these three errors are perfectly
834251881Speter     acceptable to ignore; it means that the path is now free and
835251881Speter     clear for locking, because the fsfs funcs just cleared out both
836251881Speter     of the tables for us.   */
837251881Speter  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
838251881Speter  if (existing_lock)
839251881Speter    {
840251881Speter      if (! lb->steal_lock)
841251881Speter        {
842251881Speter          /* Sorry, the path is already locked. */
843251881Speter          return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
844251881Speter        }
845251881Speter      else
846251881Speter        {
847251881Speter          /* STEAL_LOCK was passed, so fs_username is "stealing" the
848251881Speter             lock from lock->owner.  Destroy the existing lock. */
849251881Speter          SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
850251881Speter        }
851251881Speter    }
852251881Speter
853251881Speter  /* Create our new lock, and add it to the tables.
854251881Speter     Ensure that the lock is created in the correct pool. */
855251881Speter  lock = svn_lock_create(lb->pool);
856251881Speter  if (lb->token)
857251881Speter    lock->token = apr_pstrdup(lb->pool, lb->token);
858251881Speter  else
859251881Speter    SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
860251881Speter                                           lb->pool));
861251881Speter  lock->path = apr_pstrdup(lb->pool, lb->path);
862251881Speter  lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
863251881Speter  lock->comment = apr_pstrdup(lb->pool, lb->comment);
864251881Speter  lock->is_dav_comment = lb->is_dav_comment;
865251881Speter  lock->creation_date = apr_time_now();
866251881Speter  lock->expiration_date = lb->expiration_date;
867251881Speter  SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
868251881Speter  SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
869251881Speter  *lb->lock_p = lock;
870251881Speter
871251881Speter  return SVN_NO_ERROR;
872251881Speter}
873251881Speter
874251881Speter/* Baton used for unlock_body below. */
875251881Speterstruct unlock_baton {
876251881Speter  svn_fs_t *fs;
877251881Speter  const char *path;
878251881Speter  const char *token;
879251881Speter  svn_boolean_t break_lock;
880251881Speter};
881251881Speter
882251881Speter/* This implements the svn_fs_fs__with_write_lock() 'body' callback
883251881Speter   type, and assumes that the write lock is held.
884251881Speter   BATON is a 'struct unlock_baton *'. */
885251881Speterstatic svn_error_t *
886251881Speterunlock_body(void *baton, apr_pool_t *pool)
887251881Speter{
888251881Speter  struct unlock_baton *ub = baton;
889251881Speter  svn_lock_t *lock;
890251881Speter
891251881Speter  /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
892251881Speter  SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
893251881Speter
894251881Speter  /* Unless breaking the lock, we do some checks. */
895251881Speter  if (! ub->break_lock)
896251881Speter    {
897251881Speter      /* Sanity check:  the incoming token should match lock->token. */
898251881Speter      if (strcmp(ub->token, lock->token) != 0)
899251881Speter        return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
900251881Speter
901251881Speter      /* There better be a username attached to the fs. */
902251881Speter      if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
903251881Speter        return SVN_FS__ERR_NO_USER(ub->fs);
904251881Speter
905251881Speter      /* And that username better be the same as the lock's owner. */
906251881Speter      if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
907251881Speter        return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
908251881Speter           ub->fs, ub->fs->access_ctx->username, lock->owner);
909251881Speter    }
910251881Speter
911251881Speter  /* Remove lock and lock token files. */
912251881Speter  return delete_lock(ub->fs, lock, pool);
913251881Speter}
914251881Speter
915251881Speter
916251881Speter/*** Public API implementations ***/
917251881Speter
918251881Spetersvn_error_t *
919251881Spetersvn_fs_fs__lock(svn_lock_t **lock_p,
920251881Speter                svn_fs_t *fs,
921251881Speter                const char *path,
922251881Speter                const char *token,
923251881Speter                const char *comment,
924251881Speter                svn_boolean_t is_dav_comment,
925251881Speter                apr_time_t expiration_date,
926251881Speter                svn_revnum_t current_rev,
927251881Speter                svn_boolean_t steal_lock,
928251881Speter                apr_pool_t *pool)
929251881Speter{
930251881Speter  struct lock_baton lb;
931251881Speter
932251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
933251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
934251881Speter
935251881Speter  lb.lock_p = lock_p;
936251881Speter  lb.fs = fs;
937251881Speter  lb.path = path;
938251881Speter  lb.token = token;
939251881Speter  lb.comment = comment;
940251881Speter  lb.is_dav_comment = is_dav_comment;
941251881Speter  lb.expiration_date = expiration_date;
942251881Speter  lb.current_rev = current_rev;
943251881Speter  lb.steal_lock = steal_lock;
944251881Speter  lb.pool = pool;
945251881Speter
946251881Speter  return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
947251881Speter}
948251881Speter
949251881Speter
950251881Spetersvn_error_t *
951251881Spetersvn_fs_fs__generate_lock_token(const char **token,
952251881Speter                               svn_fs_t *fs,
953251881Speter                               apr_pool_t *pool)
954251881Speter{
955251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
956251881Speter
957251881Speter  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
958251881Speter     want to use the fs UUID + some incremented number?  For now, we
959251881Speter     generate a URI that matches the DAV RFC.  We could change this to
960251881Speter     some other URI scheme someday, if we wish. */
961251881Speter  *token = apr_pstrcat(pool, "opaquelocktoken:",
962251881Speter                       svn_uuid_generate(pool), (char *)NULL);
963251881Speter  return SVN_NO_ERROR;
964251881Speter}
965251881Speter
966251881Speter
967251881Spetersvn_error_t *
968251881Spetersvn_fs_fs__unlock(svn_fs_t *fs,
969251881Speter                  const char *path,
970251881Speter                  const char *token,
971251881Speter                  svn_boolean_t break_lock,
972251881Speter                  apr_pool_t *pool)
973251881Speter{
974251881Speter  struct unlock_baton ub;
975251881Speter
976251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
977251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
978251881Speter
979251881Speter  ub.fs = fs;
980251881Speter  ub.path = path;
981251881Speter  ub.token = token;
982251881Speter  ub.break_lock = break_lock;
983251881Speter
984251881Speter  return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
985251881Speter}
986251881Speter
987251881Speter
988251881Spetersvn_error_t *
989251881Spetersvn_fs_fs__get_lock(svn_lock_t **lock_p,
990251881Speter                    svn_fs_t *fs,
991251881Speter                    const char *path,
992251881Speter                    apr_pool_t *pool)
993251881Speter{
994251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
995251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
996251881Speter  return get_lock_helper(fs, lock_p, path, FALSE, pool);
997251881Speter}
998251881Speter
999251881Speter
1000251881Speter/* Baton for get_locks_filter_func(). */
1001251881Spetertypedef struct get_locks_filter_baton_t
1002251881Speter{
1003251881Speter  const char *path;
1004251881Speter  svn_depth_t requested_depth;
1005251881Speter  svn_fs_get_locks_callback_t get_locks_func;
1006251881Speter  void *get_locks_baton;
1007251881Speter
1008251881Speter} get_locks_filter_baton_t;
1009251881Speter
1010251881Speter
1011251881Speter/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
1012251881Speter   which filters out locks on paths that aren't within
1013251881Speter   BATON->requested_depth of BATON->path before called
1014251881Speter   BATON->get_locks_func() with BATON->get_locks_baton.
1015251881Speter
1016251881Speter   NOTE: See issue #3660 for details about how the FSFS lock
1017251881Speter   management code is inconsistent.  Until that inconsistency is
1018251881Speter   resolved, we take this filtering approach rather than honoring
1019251881Speter   depth requests closer to the crawling code.  In other words, once
1020251881Speter   we decide how to resolve issue #3660, there might be a more
1021251881Speter   performant way to honor the depth passed to svn_fs_fs__get_locks().  */
1022251881Speterstatic svn_error_t *
1023251881Speterget_locks_filter_func(void *baton,
1024251881Speter                      svn_lock_t *lock,
1025251881Speter                      apr_pool_t *pool)
1026251881Speter{
1027251881Speter  get_locks_filter_baton_t *b = baton;
1028251881Speter
1029251881Speter  /* Filter out unwanted paths.  Since Subversion only allows
1030251881Speter     locks on files, we can treat depth=immediates the same as
1031251881Speter     depth=files for filtering purposes.  Meaning, we'll keep
1032251881Speter     this lock if:
1033251881Speter
1034251881Speter     a) its path is the very path we queried, or
1035251881Speter     b) we've asked for a fully recursive answer, or
1036251881Speter     c) we've asked for depth=files or depth=immediates, and this
1037251881Speter        lock is on an immediate child of our query path.
1038251881Speter  */
1039251881Speter  if ((strcmp(b->path, lock->path) == 0)
1040251881Speter      || (b->requested_depth == svn_depth_infinity))
1041251881Speter    {
1042251881Speter      SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1043251881Speter    }
1044251881Speter  else if ((b->requested_depth == svn_depth_files) ||
1045251881Speter           (b->requested_depth == svn_depth_immediates))
1046251881Speter    {
1047251881Speter      const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1048251881Speter      if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1049251881Speter        SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1050251881Speter    }
1051251881Speter
1052251881Speter  return SVN_NO_ERROR;
1053251881Speter}
1054251881Speter
1055251881Spetersvn_error_t *
1056251881Spetersvn_fs_fs__get_locks(svn_fs_t *fs,
1057251881Speter                     const char *path,
1058251881Speter                     svn_depth_t depth,
1059251881Speter                     svn_fs_get_locks_callback_t get_locks_func,
1060251881Speter                     void *get_locks_baton,
1061251881Speter                     apr_pool_t *pool)
1062251881Speter{
1063251881Speter  const char *digest_path;
1064251881Speter  get_locks_filter_baton_t glfb;
1065251881Speter
1066251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1067251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
1068251881Speter
1069251881Speter  glfb.path = path;
1070251881Speter  glfb.requested_depth = depth;
1071251881Speter  glfb.get_locks_func = get_locks_func;
1072251881Speter  glfb.get_locks_baton = get_locks_baton;
1073251881Speter
1074251881Speter  /* Get the top digest path in our tree of interest, and then walk it. */
1075251881Speter  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1076251881Speter  SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1077251881Speter                     FALSE, pool));
1078251881Speter  return SVN_NO_ERROR;
1079251881Speter}
1080