lock.c revision 362181
1/* lock.c :  functions for manipulating filesystem locks.
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23
24#include "svn_hash.h"
25#include "svn_pools.h"
26#include "svn_error.h"
27#include "svn_fs.h"
28#include "svn_private_config.h"
29
30#include <apr_uuid.h>
31
32#include "lock.h"
33#include "tree.h"
34#include "err.h"
35#include "bdb/locks-table.h"
36#include "bdb/lock-tokens-table.h"
37#include "util/fs_skels.h"
38#include "../libsvn_fs/fs-loader.h"
39#include "private/svn_fs_util.h"
40#include "private/svn_subr_private.h"
41#include "private/svn_dep_compat.h"
42#include "revs-txns.h"
43
44
45/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
46   part of TRAIL. */
47static svn_error_t *
48add_lock_and_token(svn_lock_t *lock,
49                   const char *lock_token,
50                   const char *path,
51                   trail_t *trail)
52{
53  SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
54                               trail, trail->pool));
55  return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
56                                    trail, trail->pool);
57}
58
59
60/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
61   whose KIND is supplied), as part of TRAIL. */
62static svn_error_t *
63delete_lock_and_token(const char *lock_token,
64                      const char *path,
65                      trail_t *trail)
66{
67  SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
68                                  trail, trail->pool));
69  return svn_fs_bdb__lock_token_delete(trail->fs, path,
70                                       trail, trail->pool);
71}
72
73
74/* The effective arguments for txn_body_lock() below. */
75struct lock_args
76{
77  svn_lock_t **lock_p;
78  const char *path;
79  const char *token;
80  const char *comment;
81  svn_boolean_t is_dav_comment;
82  svn_boolean_t steal_lock;
83  apr_time_t expiration_date;
84  svn_revnum_t current_rev;
85  apr_pool_t *result_pool;
86};
87
88
89/* The body of svn_fs_base__lock(), which see.
90
91   BATON is a 'struct lock_args *' holding the effective arguments.
92   BATON->path is the canonical abspath to lock.  Set *BATON->lock_p
93   to the resulting lock.  For the other arguments, see
94   svn_fs_lock_many().
95
96   This implements the svn_fs_base__retry_txn() 'body' callback type.
97 */
98static svn_error_t *
99txn_body_lock(void *baton, trail_t *trail)
100{
101  struct lock_args *args = baton;
102  svn_node_kind_t kind = svn_node_file;
103  svn_lock_t *existing_lock;
104  svn_lock_t *lock;
105
106  *args->lock_p = NULL;
107
108  SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
109
110  /* Until we implement directory locks someday, we only allow locks
111     on files. */
112  if (kind == svn_node_dir)
113    return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
114
115  /* While our locking implementation easily supports the locking of
116     nonexistent paths, we deliberately choose not to allow such madness. */
117  if (kind == svn_node_none)
118    {
119      if (SVN_IS_VALID_REVNUM(args->current_rev))
120        return svn_error_createf(
121          SVN_ERR_FS_OUT_OF_DATE, NULL,
122          _("Path '%s' doesn't exist in HEAD revision"),
123          args->path);
124      else
125        return svn_error_createf(
126          SVN_ERR_FS_NOT_FOUND, NULL,
127          _("Path '%s' doesn't exist in HEAD revision"),
128          args->path);
129    }
130
131  /* There better be a username attached to the fs. */
132  if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
133    return SVN_FS__ERR_NO_USER(trail->fs);
134
135  /* Is the caller attempting to lock an out-of-date working file? */
136  if (SVN_IS_VALID_REVNUM(args->current_rev))
137    {
138      svn_revnum_t created_rev;
139      SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
140                                                trail, trail->pool));
141
142      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
143         apparently somebody is trying to lock something in their
144         working copy, but somebody else has deleted the thing
145         from HEAD.  That counts as being 'out of date'. */
146      if (! SVN_IS_VALID_REVNUM(created_rev))
147        return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
148                                 "Path '%s' doesn't exist in HEAD revision",
149                                 args->path);
150
151      if (args->current_rev < created_rev)
152        return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
153                                 "Lock failed: newer version of '%s' exists",
154                                 args->path);
155    }
156
157  /* If the caller provided a TOKEN, we *really* need to see
158     if a lock already exists with that token, and if so, verify that
159     the lock's path matches PATH.  Otherwise we run the risk of
160     breaking the 1-to-1 mapping of lock tokens to locked paths. */
161  if (args->token)
162    {
163      svn_lock_t *lock_from_token;
164      svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
165                                              args->token, trail,
166                                              trail->pool);
167      if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
168                  || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
169        {
170          svn_error_clear(err);
171        }
172      else
173        {
174          SVN_ERR(err);
175          if (strcmp(lock_from_token->path, args->path) != 0)
176            return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
177                                    "Lock failed: token refers to existing "
178                                    "lock with non-matching path.");
179        }
180    }
181
182  /* Is the path already locked?
183
184     Note that this next function call will automatically ignore any
185     errors about {the path not existing as a key, the path's token
186     not existing as a key, the lock just having been expired}.  And
187     that's totally fine.  Any of these three errors are perfectly
188     acceptable to ignore; it means that the path is now free and
189     clear for locking, because the bdb funcs just cleared out both
190     of the tables for us.   */
191  SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
192                                       trail, trail->pool));
193  if (existing_lock)
194    {
195      if (! args->steal_lock)
196        {
197          /* Sorry, the path is already locked. */
198          return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
199                                                 existing_lock);
200        }
201      else
202        {
203          /* ARGS->steal_lock is set, so fs_username is "stealing" the
204             lock from lock->owner.  Destroy the existing lock. */
205          SVN_ERR(delete_lock_and_token(existing_lock->token,
206                                        existing_lock->path, trail));
207        }
208    }
209
210  /* Create a new lock, and add it to the tables. */
211  lock = svn_lock_create(args->result_pool);
212  if (args->token)
213    lock->token = apr_pstrdup(args->result_pool, args->token);
214  else
215    SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
216                                             args->result_pool));
217  lock->path = args->path; /* Already in result_pool. */
218  lock->owner = apr_pstrdup(args->result_pool, trail->fs->access_ctx->username);
219  lock->comment = apr_pstrdup(args->result_pool, args->comment);
220  lock->is_dav_comment = args->is_dav_comment;
221  lock->creation_date = apr_time_now();
222  lock->expiration_date = args->expiration_date;
223  SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
224  *(args->lock_p) = lock;
225
226  return SVN_NO_ERROR;
227}
228
229
230
231svn_error_t *
232svn_fs_base__lock(svn_fs_t *fs,
233                  apr_hash_t *targets,
234                  const char *comment,
235                  svn_boolean_t is_dav_comment,
236                  apr_time_t expiration_date,
237                  svn_boolean_t steal_lock,
238                  svn_fs_lock_callback_t lock_callback,
239                  void *lock_baton,
240                  apr_pool_t *result_pool,
241                  apr_pool_t *scratch_pool)
242{
243  apr_hash_index_t *hi;
244  svn_error_t *cb_err = SVN_NO_ERROR;
245  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
246  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
247
248  SVN_ERR(svn_fs__check_fs(fs, TRUE));
249  SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, scratch_pool));
250
251  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
252    {
253      struct lock_args args;
254      const char *path = apr_hash_this_key(hi);
255      const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
256      svn_lock_t *lock;
257      svn_error_t *err = NULL;
258
259      svn_pool_clear(iterpool);
260      args.lock_p = &lock;
261      args.path = svn_fs__canonicalize_abspath(path, result_pool);
262      args.token = target->token;
263      args.comment = comment;
264      args.is_dav_comment = is_dav_comment;
265      args.steal_lock = steal_lock;
266      args.expiration_date = expiration_date;
267      args.current_rev = target->current_rev;
268      args.result_pool = result_pool;
269
270      if (SVN_IS_VALID_REVNUM(target->current_rev))
271        {
272          if (target->current_rev > youngest_rev)
273            err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
274                                    _("No such revision %ld"),
275                                    target->current_rev);
276        }
277
278      if (!err)
279        err = svn_fs_base__retry_txn(fs, txn_body_lock, &args, TRUE,
280                                     iterpool);
281      if (!cb_err && lock_callback)
282        cb_err = lock_callback(lock_baton, args.path, lock, err, iterpool);
283      svn_error_clear(err);
284    }
285  svn_pool_destroy(iterpool);
286
287  return svn_error_trace(cb_err);
288}
289
290
291svn_error_t *
292svn_fs_base__generate_lock_token(const char **token,
293                                 svn_fs_t *fs,
294                                 apr_pool_t *pool)
295{
296  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
297     want to use the fs UUID + some incremented number?  For now, we
298     generate a URI that matches the DAV RFC.  We could change this to
299     some other URI scheme someday, if we wish. */
300  *token = apr_pstrcat(pool, "opaquelocktoken:",
301                       svn_uuid_generate(pool), SVN_VA_NULL);
302  return SVN_NO_ERROR;
303}
304
305
306/* The effective arguments for txn_body_unlock() below. */
307struct unlock_args
308{
309  const char *path;
310  const char *token;
311  svn_boolean_t break_lock;
312};
313
314
315/* The body of svn_fs_base__unlock(), which see.
316
317   BATON is a 'struct unlock_args *' holding the effective arguments.
318   BATON->path is the canonical path and BATON->token is the token.
319   For the other arguments, see svn_fs_unlock_many().
320
321   This implements the svn_fs_base__retry_txn() 'body' callback type.
322 */
323static svn_error_t *
324txn_body_unlock(void *baton, trail_t *trail)
325{
326  struct unlock_args *args = baton;
327  const char *lock_token;
328  svn_lock_t *lock;
329
330  /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
331  SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
332                                     trail, trail->pool));
333
334  /* If not breaking the lock, we need to do some more checking. */
335  if (!args->break_lock)
336    {
337      /* Sanity check: The lock token must exist, and must match. */
338      if (args->token == NULL)
339        return svn_fs_base__err_no_lock_token(trail->fs, args->path);
340      else if (strcmp(lock_token, args->token) != 0)
341        return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
342
343      SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
344                                   trail, trail->pool));
345
346      /* There better be a username attached to the fs. */
347      if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
348        return SVN_FS__ERR_NO_USER(trail->fs);
349
350      /* And that username better be the same as the lock's owner. */
351      if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
352        return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
353           trail->fs,
354           trail->fs->access_ctx->username,
355           lock->owner);
356    }
357
358  /* Remove a row from each of the locking tables. */
359  return delete_lock_and_token(lock_token, args->path, trail);
360}
361
362
363svn_error_t *
364svn_fs_base__unlock(svn_fs_t *fs,
365                    apr_hash_t *targets,
366                    svn_boolean_t break_lock,
367                    svn_fs_lock_callback_t lock_callback,
368                    void *lock_baton,
369                    apr_pool_t *result_pool,
370                    apr_pool_t *scratch_pool)
371{
372  apr_hash_index_t *hi;
373  svn_error_t *cb_err = SVN_NO_ERROR;
374  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
375
376  SVN_ERR(svn_fs__check_fs(fs, TRUE));
377
378  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
379    {
380      struct unlock_args args;
381      const char *path = apr_hash_this_key(hi);
382      const char *token = apr_hash_this_val(hi);
383      svn_error_t *err;
384
385      svn_pool_clear(iterpool);
386      args.path = svn_fs__canonicalize_abspath(path, result_pool);
387      args.token = token;
388      args.break_lock = break_lock;
389
390      err = svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE,
391                                   iterpool);
392      if (!cb_err && lock_callback)
393        cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
394      svn_error_clear(err);
395    }
396  svn_pool_destroy(iterpool);
397
398  return svn_error_trace(cb_err);
399}
400
401
402svn_error_t *
403svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
404                             const char *path,
405                             trail_t *trail,
406                             apr_pool_t *pool)
407{
408  const char *lock_token;
409  svn_error_t *err;
410
411  err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
412                                   trail, pool);
413
414  /* We've deliberately decided that this function doesn't tell the
415     caller *why* the lock is unavailable.  */
416  if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
417              || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
418              || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
419    {
420      svn_error_clear(err);
421      *lock_p = NULL;
422      return SVN_NO_ERROR;
423    }
424  else
425    SVN_ERR(err);
426
427  /* Same situation here.  */
428  err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
429  if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
430              || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
431    {
432      svn_error_clear(err);
433      *lock_p = NULL;
434      return SVN_NO_ERROR;
435    }
436  else
437    SVN_ERR(err);
438
439  return svn_error_trace(err);
440}
441
442
443struct lock_token_get_args
444{
445  svn_lock_t **lock_p;
446  const char *path;
447};
448
449
450static svn_error_t *
451txn_body_get_lock(void *baton, trail_t *trail)
452{
453  struct lock_token_get_args *args = baton;
454  return svn_fs_base__get_lock_helper(args->lock_p, args->path,
455                                      trail, trail->pool);
456}
457
458
459svn_error_t *
460svn_fs_base__get_lock(svn_lock_t **lock,
461                      svn_fs_t *fs,
462                      const char *path,
463                      apr_pool_t *pool)
464{
465  struct lock_token_get_args args;
466
467  SVN_ERR(svn_fs__check_fs(fs, TRUE));
468
469  args.path = svn_fs__canonicalize_abspath(path, pool);
470  args.lock_p = lock;
471  return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
472}
473
474/* Implements `svn_fs_get_locks_callback_t', spooling lock information
475   to a stream as the filesystem provides it.  BATON is an 'svn_stream_t *'
476   object pointing to the stream.  We'll write the spool stream with a
477   format like so:
478
479      SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
480
481   where each skel is a lock skel (the same format we use to store
482   locks in the `locks' table). */
483static svn_error_t *
484spool_locks_info(void *baton,
485                 svn_lock_t *lock,
486                 apr_pool_t *pool)
487{
488  svn_skel_t *lock_skel;
489  svn_stream_t *stream = baton;
490  const char *skel_len;
491  svn_stringbuf_t *skel_buf;
492  apr_size_t len;
493
494  SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
495  skel_buf = svn_skel__unparse(lock_skel, pool);
496  skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
497  len = strlen(skel_len);
498  SVN_ERR(svn_stream_write(stream, skel_len, &len));
499  len = skel_buf->len;
500  SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
501  len = 1;
502  return svn_stream_write(stream, "\n", &len);
503}
504
505
506struct locks_get_args
507{
508  const char *path;
509  svn_depth_t depth;
510  svn_stream_t *stream;
511};
512
513
514static svn_error_t *
515txn_body_get_locks(void *baton, trail_t *trail)
516{
517  struct locks_get_args *args = baton;
518  return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
519                               spool_locks_info, args->stream,
520                               trail, trail->pool);
521}
522
523
524svn_error_t *
525svn_fs_base__get_locks(svn_fs_t *fs,
526                       const char *path,
527                       svn_depth_t depth,
528                       svn_fs_get_locks_callback_t get_locks_func,
529                       void *get_locks_baton,
530                       apr_pool_t *pool)
531{
532  struct locks_get_args args;
533  svn_stream_t *stream;
534  svn_stringbuf_t *buf;
535  svn_boolean_t eof;
536  apr_pool_t *iterpool = svn_pool_create(pool);
537
538  SVN_ERR(svn_fs__check_fs(fs, TRUE));
539
540  args.path = svn_fs__canonicalize_abspath(path, pool);
541  args.depth = depth;
542  /* Enough for 100+ locks if the comments are small. */
543  args.stream = svn_stream__from_spillbuf(svn_spillbuf__create(4 * 1024  /* blocksize */,
544                                                               64 * 1024 /* maxsize */,
545                                                               pool),
546                                          pool);
547  SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
548
549  /* Read the stream calling GET_LOCKS_FUNC(). */
550  stream = args.stream;
551
552  while (1)
553    {
554      apr_size_t len, skel_len;
555      char c, *skel_buf;
556      svn_skel_t *lock_skel;
557      svn_lock_t *lock;
558      apr_uint64_t ui64;
559      svn_error_t *err;
560
561      svn_pool_clear(iterpool);
562
563      /* Read a skel length line and parse it for the skel's length.  */
564      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
565      if (eof)
566        break;
567      err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
568      if (err)
569        return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
570      skel_len = (apr_size_t)ui64;
571
572      /* Now read that much into a buffer. */
573      skel_buf = apr_palloc(pool, skel_len + 1);
574      SVN_ERR(svn_stream_read_full(stream, skel_buf, &skel_len));
575      skel_buf[skel_len] = '\0';
576
577      /* Read the extra newline that follows the skel. */
578      len = 1;
579      SVN_ERR(svn_stream_read_full(stream, &c, &len));
580      if (c != '\n')
581        return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
582
583      /* Parse the skel into a lock, and notify the caller. */
584      lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
585      SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
586      SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
587    }
588
589  SVN_ERR(svn_stream_close(stream));
590  svn_pool_destroy(iterpool);
591  return SVN_NO_ERROR;
592}
593
594
595
596/* Utility function:  verify that a lock can be used.
597
598   If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
599
600   If the FS username doesn't match LOCK's owner, return
601   SVN_ERR_FS_LOCK_OWNER_MISMATCH.
602
603   If FS hasn't been supplied with a matching lock-token for LOCK,
604   return SVN_ERR_FS_BAD_LOCK_TOKEN.
605
606   Otherwise return SVN_NO_ERROR.
607 */
608static svn_error_t *
609verify_lock(svn_fs_t *fs,
610            svn_lock_t *lock,
611            apr_pool_t *pool)
612{
613  if ((! fs->access_ctx) || (! fs->access_ctx->username))
614    return svn_error_createf
615      (SVN_ERR_FS_NO_USER, NULL,
616       _("Cannot verify lock on path '%s'; no username available"),
617       lock->path);
618
619  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
620    return svn_error_createf
621      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
622       _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
623       fs->access_ctx->username, lock->path, lock->owner);
624
625  else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
626    return svn_error_createf
627      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
628       _("Cannot verify lock on path '%s'; no matching lock-token available"),
629       lock->path);
630
631  return SVN_NO_ERROR;
632}
633
634
635/* This implements the svn_fs_get_locks_callback_t interface, where
636   BATON is just an svn_fs_t object. */
637static svn_error_t *
638get_locks_callback(void *baton,
639                   svn_lock_t *lock,
640                   apr_pool_t *pool)
641{
642  return verify_lock(baton, lock, pool);
643}
644
645
646/* The main routine for lock enforcement, used throughout libsvn_fs_base. */
647svn_error_t *
648svn_fs_base__allow_locked_operation(const char *path,
649                                    svn_boolean_t recurse,
650                                    trail_t *trail,
651                                    apr_pool_t *pool)
652{
653  if (recurse)
654    {
655      /* Discover all locks at or below the path. */
656      SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
657                                    get_locks_callback,
658                                    trail->fs, trail, pool));
659    }
660  else
661    {
662      svn_lock_t *lock;
663
664      /* Discover any lock attached to the path. */
665      SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
666      if (lock)
667        SVN_ERR(verify_lock(trail->fs, lock, pool));
668    }
669  return SVN_NO_ERROR;
670}
671