]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_base/lock.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_base / lock.c
1 /* lock.c :  functions for manipulating filesystem locks.
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23
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. */
47 static svn_error_t *
48 add_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. */
62 static svn_error_t *
63 delete_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. */
75 struct 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  */
98 static svn_error_t *
99 txn_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 or non-existent paths. */
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
231 svn_error_t *
232 svn_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
291 svn_error_t *
292 svn_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. */
307 struct 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  */
323 static svn_error_t *
324 txn_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
363 svn_error_t *
364 svn_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
402 svn_error_t *
403 svn_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
443 struct lock_token_get_args
444 {
445   svn_lock_t **lock_p;
446   const char *path;
447 };
448
449
450 static svn_error_t *
451 txn_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
459 svn_error_t *
460 svn_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). */
483 static svn_error_t *
484 spool_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
506 struct locks_get_args
507 {
508   const char *path;
509   svn_depth_t depth;
510   svn_stream_t *stream;
511 };
512
513
514 static svn_error_t *
515 txn_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
524 svn_error_t *
525 svn_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  */
608 static svn_error_t *
609 verify_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. */
637 static svn_error_t *
638 get_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. */
647 svn_error_t *
648 svn_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 }