]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs_fs/lock.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_fs_fs / 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_pools.h"
25 #include "svn_error.h"
26 #include "svn_dirent_uri.h"
27 #include "svn_path.h"
28 #include "svn_fs.h"
29 #include "svn_hash.h"
30 #include "svn_time.h"
31 #include "svn_utf.h"
32
33 #include <apr_uuid.h>
34 #include <apr_file_io.h>
35 #include <apr_file_info.h>
36
37 #include "lock.h"
38 #include "tree.h"
39 #include "fs_fs.h"
40 #include "../libsvn_fs/fs-loader.h"
41
42 #include "private/svn_fs_util.h"
43 #include "private/svn_fspath.h"
44 #include "svn_private_config.h"
45
46 /* Names of hash keys used to store a lock for writing to disk. */
47 #define PATH_KEY "path"
48 #define TOKEN_KEY "token"
49 #define OWNER_KEY "owner"
50 #define CREATION_DATE_KEY "creation_date"
51 #define EXPIRATION_DATE_KEY "expiration_date"
52 #define COMMENT_KEY "comment"
53 #define IS_DAV_COMMENT_KEY "is_dav_comment"
54 #define CHILDREN_KEY "children"
55
56 /* Number of characters from the head of a digest file name used to
57    calculate a subdirectory in which to drop that file. */
58 #define DIGEST_SUBDIR_LEN 3
59
60
61 \f
62 /*** Generic helper functions. ***/
63
64 /* Set *DIGEST to the MD5 hash of STR. */
65 static svn_error_t *
66 make_digest(const char **digest,
67             const char *str,
68             apr_pool_t *pool)
69 {
70   svn_checksum_t *checksum;
71
72   SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
73
74   *digest = svn_checksum_to_cstring_display(checksum, pool);
75   return SVN_NO_ERROR;
76 }
77
78
79 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
80    if unknown) to an svn_string_t-ized version of VALUE (whose size is
81    VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
82    will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
83    is NULL, this function will do nothing. */
84 static void
85 hash_store(apr_hash_t *hash,
86            const char *key,
87            apr_ssize_t key_len,
88            const char *value,
89            apr_ssize_t value_len,
90            apr_pool_t *pool)
91 {
92   if (! (key && value))
93     return;
94   if (value_len == APR_HASH_KEY_STRING)
95     value_len = strlen(value);
96   apr_hash_set(hash, key, key_len,
97                svn_string_ncreate(value, value_len, pool));
98 }
99
100
101 /* Fetch the value of KEY from HASH, returning only the cstring data
102    of that value (if it exists). */
103 static const char *
104 hash_fetch(apr_hash_t *hash,
105            const char *key,
106            apr_pool_t *pool)
107 {
108   svn_string_t *str = svn_hash_gets(hash, key);
109   return str ? str->data : NULL;
110 }
111
112
113 /* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt.  */
114 static svn_error_t *
115 err_corrupt_lockfile(const char *fs_path, const char *path)
116 {
117   return
118     svn_error_createf(
119      SVN_ERR_FS_CORRUPT, 0,
120      _("Corrupt lockfile for path '%s' in filesystem '%s'"),
121      path, fs_path);
122 }
123
124 \f
125 /*** Digest file handling functions. ***/
126
127 /* Return the path of the lock/entries file for which DIGEST is the
128    hashed repository relative path. */
129 static const char *
130 digest_path_from_digest(const char *fs_path,
131                         const char *digest,
132                         apr_pool_t *pool)
133 {
134   return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
135                               apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
136                               digest, NULL);
137 }
138
139
140 /* Set *DIGEST_PATH to the path to the lock/entries digest file associate
141    with PATH, where PATH is the path to the lock file or lock entries file
142    in FS. */
143 static svn_error_t *
144 digest_path_from_path(const char **digest_path,
145                       const char *fs_path,
146                       const char *path,
147                       apr_pool_t *pool)
148 {
149   const char *digest;
150   SVN_ERR(make_digest(&digest, path, pool));
151   *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
152                                       apr_pstrmemdup(pool, digest,
153                                                      DIGEST_SUBDIR_LEN),
154                                       digest, NULL);
155   return SVN_NO_ERROR;
156 }
157
158
159 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
160    empty, if the versioned path in FS represented by DIGEST_PATH has
161    no children) and LOCK (which may be NULL if that versioned path is
162    lock itself locked).  Set the permissions of DIGEST_PATH to those of
163    PERMS_REFERENCE.  Use POOL for all allocations.
164  */
165 static svn_error_t *
166 write_digest_file(apr_hash_t *children,
167                   svn_lock_t *lock,
168                   const char *fs_path,
169                   const char *digest_path,
170                   const char *perms_reference,
171                   apr_pool_t *pool)
172 {
173   svn_error_t *err = SVN_NO_ERROR;
174   svn_stream_t *stream;
175   apr_hash_index_t *hi;
176   apr_hash_t *hash = apr_hash_make(pool);
177   const char *tmp_path;
178
179   SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
180                                                        pool), fs_path, pool));
181   SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
182                                        fs_path, pool));
183
184   if (lock)
185     {
186       const char *creation_date = NULL, *expiration_date = NULL;
187       if (lock->creation_date)
188         creation_date = svn_time_to_cstring(lock->creation_date, pool);
189       if (lock->expiration_date)
190         expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
191       hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
192                  lock->path, APR_HASH_KEY_STRING, pool);
193       hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
194                  lock->token, APR_HASH_KEY_STRING, pool);
195       hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
196                  lock->owner, APR_HASH_KEY_STRING, pool);
197       hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
198                  lock->comment, APR_HASH_KEY_STRING, pool);
199       hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
200                  lock->is_dav_comment ? "1" : "0", 1, pool);
201       hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
202                  creation_date, APR_HASH_KEY_STRING, pool);
203       hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
204                  expiration_date, APR_HASH_KEY_STRING, pool);
205     }
206   if (apr_hash_count(children))
207     {
208       svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool);
209       for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
210         {
211           svn_stringbuf_appendbytes(children_list,
212                                     svn__apr_hash_index_key(hi),
213                                     svn__apr_hash_index_klen(hi));
214           svn_stringbuf_appendbyte(children_list, '\n');
215         }
216       hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
217                  children_list->data, children_list->len, pool);
218     }
219
220   SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
221                                  svn_dirent_dirname(digest_path, pool),
222                                  svn_io_file_del_none, pool, pool));
223   if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
224     {
225       svn_error_clear(svn_stream_close(stream));
226       return svn_error_createf(err->apr_err,
227                                err,
228                                _("Cannot write lock/entries hashfile '%s'"),
229                                svn_dirent_local_style(tmp_path, pool));
230     }
231
232   SVN_ERR(svn_stream_close(stream));
233   SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
234   SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
235   return SVN_NO_ERROR;
236 }
237
238
239 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
240    file (if it exists, and if *LOCK_P is non-NULL) and the hash of
241    CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL).  Use POOL
242    for all allocations.  */
243 static svn_error_t *
244 read_digest_file(apr_hash_t **children_p,
245                  svn_lock_t **lock_p,
246                  const char *fs_path,
247                  const char *digest_path,
248                  apr_pool_t *pool)
249 {
250   svn_error_t *err = SVN_NO_ERROR;
251   svn_lock_t *lock;
252   apr_hash_t *hash;
253   svn_stream_t *stream;
254   const char *val;
255
256   if (lock_p)
257     *lock_p = NULL;
258   if (children_p)
259     *children_p = apr_hash_make(pool);
260
261   err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
262   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
263     {
264       svn_error_clear(err);
265       return SVN_NO_ERROR;
266     }
267   SVN_ERR(err);
268
269   /* If our caller doesn't care about anything but the presence of the
270      file... whatever. */
271   if (! (lock_p || children_p))
272     return svn_stream_close(stream);
273
274   hash = apr_hash_make(pool);
275   if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
276     {
277       svn_error_clear(svn_stream_close(stream));
278       return svn_error_createf(err->apr_err,
279                                err,
280                                _("Can't parse lock/entries hashfile '%s'"),
281                                svn_dirent_local_style(digest_path, pool));
282     }
283   SVN_ERR(svn_stream_close(stream));
284
285   /* If our caller cares, see if we have a lock path in our hash. If
286      so, we'll assume we have a lock here. */
287   val = hash_fetch(hash, PATH_KEY, pool);
288   if (val && lock_p)
289     {
290       const char *path = val;
291
292       /* Create our lock and load it up. */
293       lock = svn_lock_create(pool);
294       lock->path = path;
295
296       if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
297         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
298
299       if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
300         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
301
302       if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
303         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
304       lock->is_dav_comment = (val[0] == '1');
305
306       if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
307         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
308       SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
309
310       if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
311         SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
312
313       lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
314
315       *lock_p = lock;
316     }
317
318   /* If our caller cares, see if we have any children for this path. */
319   val = hash_fetch(hash, CHILDREN_KEY, pool);
320   if (val && children_p)
321     {
322       apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
323       int i;
324
325       for (i = 0; i < kiddos->nelts; i++)
326         {
327           svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
328                         (void *)1);
329         }
330     }
331   return SVN_NO_ERROR;
332 }
333
334
335 \f
336 /*** Lock helper functions (path here are still FS paths, not on-disk
337      schema-supporting paths) ***/
338
339
340 /* Write LOCK in FS to the actual OS filesystem.
341
342    Use PERMS_REFERENCE for the permissions of any digest files.
343
344    Note: this takes an FS_PATH because it's called from the hotcopy logic.
345  */
346 static svn_error_t *
347 set_lock(const char *fs_path,
348          svn_lock_t *lock,
349          const char *perms_reference,
350          apr_pool_t *pool)
351 {
352   svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
353   const char *lock_digest_path = NULL;
354   apr_pool_t *subpool;
355
356   SVN_ERR_ASSERT(lock);
357
358   /* Iterate in reverse, creating the lock for LOCK->path, and then
359      just adding entries for its parent, until we reach a parent
360      that's already listed in *its* parent. */
361   subpool = svn_pool_create(pool);
362   while (1729)
363     {
364       const char *digest_path, *digest_file;
365       apr_hash_t *this_children;
366       svn_lock_t *this_lock;
367
368       svn_pool_clear(subpool);
369
370       /* Calculate the DIGEST_PATH for the currently FS path, and then
371          get its DIGEST_FILE basename. */
372       SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
373                                     subpool));
374       digest_file = svn_dirent_basename(digest_path, subpool);
375
376       SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
377                                digest_path, subpool));
378
379       /* We're either writing a new lock (first time through only) or
380          a new entry (every time but the first). */
381       if (lock)
382         {
383           this_lock = lock;
384           lock = NULL;
385           lock_digest_path = apr_pstrdup(pool, digest_file);
386         }
387       else
388         {
389           /* If we already have an entry for this path, we're done. */
390           if (svn_hash_gets(this_children, lock_digest_path))
391             break;
392           svn_hash_sets(this_children, lock_digest_path, (void *)1);
393         }
394       SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
395                                 digest_path, perms_reference, subpool));
396
397       /* Prep for next iteration, or bail if we're done. */
398       if (svn_fspath__is_root(this_path->data, this_path->len))
399         break;
400       svn_stringbuf_set(this_path,
401                         svn_fspath__dirname(this_path->data, subpool));
402     }
403
404   svn_pool_destroy(subpool);
405   return SVN_NO_ERROR;
406 }
407
408 /* Delete LOCK from FS in the actual OS filesystem. */
409 static svn_error_t *
410 delete_lock(svn_fs_t *fs,
411             svn_lock_t *lock,
412             apr_pool_t *pool)
413 {
414   svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
415   const char *child_to_kill = NULL;
416   apr_pool_t *subpool;
417
418   SVN_ERR_ASSERT(lock);
419
420   /* Iterate in reverse, deleting the lock for LOCK->path, and then
421      deleting its entry as it appears in each of its parents. */
422   subpool = svn_pool_create(pool);
423   while (1729)
424     {
425       const char *digest_path, *digest_file;
426       apr_hash_t *this_children;
427       svn_lock_t *this_lock;
428
429       svn_pool_clear(subpool);
430
431       /* Calculate the DIGEST_PATH for the currently FS path, and then
432          get its DIGEST_FILE basename. */
433       SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
434                                     subpool));
435       digest_file = svn_dirent_basename(digest_path, subpool);
436
437       SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
438                                digest_path, subpool));
439
440       /* Delete the lock (first time through only). */
441       if (lock)
442         {
443           this_lock = NULL;
444           lock = NULL;
445           child_to_kill = apr_pstrdup(pool, digest_file);
446         }
447
448       if (child_to_kill)
449         svn_hash_sets(this_children, child_to_kill, NULL);
450
451       if (! (this_lock || apr_hash_count(this_children) != 0))
452         {
453           /* Special case:  no goodz, no file.  And remember to nix
454              the entry for it in its parent. */
455           SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
456         }
457       else
458         {
459           const char *rev_0_path;
460           SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
461           SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
462                                     digest_path, rev_0_path, subpool));
463         }
464
465       /* Prep for next iteration, or bail if we're done. */
466       if (svn_fspath__is_root(this_path->data, this_path->len))
467         break;
468       svn_stringbuf_set(this_path,
469                         svn_fspath__dirname(this_path->data, subpool));
470     }
471
472   svn_pool_destroy(subpool);
473   return SVN_NO_ERROR;
474 }
475
476 /* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
477    TRUE if the caller (or one of its callers) has taken out the
478    repository-wide write lock, FALSE otherwise.  If MUST_EXIST is
479    not set, the function will simply return NULL in *LOCK_P instead
480    of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
481    was not found (much faster).  Use POOL for allocations. */
482 static svn_error_t *
483 get_lock(svn_lock_t **lock_p,
484          svn_fs_t *fs,
485          const char *path,
486          svn_boolean_t have_write_lock,
487          svn_boolean_t must_exist,
488          apr_pool_t *pool)
489 {
490   svn_lock_t *lock = NULL;
491   const char *digest_path;
492   svn_node_kind_t kind;
493
494   SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
495   SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
496
497   *lock_p = NULL;
498   if (kind != svn_node_none)
499     SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
500
501   if (! lock)
502     return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
503
504   /* Don't return an expired lock. */
505   if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
506     {
507       /* Only remove the lock if we have the write lock.
508          Read operations shouldn't change the filesystem. */
509       if (have_write_lock)
510         SVN_ERR(delete_lock(fs, lock, pool));
511       return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
512     }
513
514   *lock_p = lock;
515   return SVN_NO_ERROR;
516 }
517
518
519 /* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
520    TRUE if the caller (or one of its callers) has taken out the
521    repository-wide write lock, FALSE otherwise.  Use POOL for
522    allocations. */
523 static svn_error_t *
524 get_lock_helper(svn_fs_t *fs,
525                 svn_lock_t **lock_p,
526                 const char *path,
527                 svn_boolean_t have_write_lock,
528                 apr_pool_t *pool)
529 {
530   svn_lock_t *lock;
531   svn_error_t *err;
532
533   err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
534
535   /* We've deliberately decided that this function doesn't tell the
536      caller *why* the lock is unavailable.  */
537   if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
538               || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
539     {
540       svn_error_clear(err);
541       *lock_p = NULL;
542       return SVN_NO_ERROR;
543     }
544   else
545     SVN_ERR(err);
546
547   *lock_p = lock;
548   return SVN_NO_ERROR;
549 }
550
551
552 /* Baton for locks_walker(). */
553 struct walk_locks_baton {
554   svn_fs_get_locks_callback_t get_locks_func;
555   void *get_locks_baton;
556   svn_fs_t *fs;
557 };
558
559 /* Implements walk_digests_callback_t. */
560 static svn_error_t *
561 locks_walker(void *baton,
562              const char *fs_path,
563              const char *digest_path,
564              apr_hash_t *children,
565              svn_lock_t *lock,
566              svn_boolean_t have_write_lock,
567              apr_pool_t *pool)
568 {
569   struct walk_locks_baton *wlb = baton;
570
571   if (lock)
572     {
573       /* Don't report an expired lock. */
574       if (lock->expiration_date == 0
575           || (apr_time_now() <= lock->expiration_date))
576         {
577           if (wlb->get_locks_func)
578             SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
579         }
580       else
581         {
582           /* Only remove the lock if we have the write lock.
583              Read operations shouldn't change the filesystem. */
584           if (have_write_lock)
585             SVN_ERR(delete_lock(wlb->fs, lock, pool));
586         }
587     }
588
589   return SVN_NO_ERROR;
590 }
591
592 /* Callback type for walk_digest_files().
593  *
594  * CHILDREN and LOCK come from a read_digest_file(digest_path) call.
595  */
596 typedef svn_error_t *(*walk_digests_callback_t)(void *baton,
597                                                 const char *fs_path,
598                                                 const char *digest_path,
599                                                 apr_hash_t *children,
600                                                 svn_lock_t *lock,
601                                                 svn_boolean_t have_write_lock,
602                                                 apr_pool_t *pool);
603
604 /* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
605    all lock digest files in and under PATH in FS.
606    HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
607    has the FS write lock. */
608 static svn_error_t *
609 walk_digest_files(const char *fs_path,
610                   const char *digest_path,
611                   walk_digests_callback_t walk_digests_func,
612                   void *walk_digests_baton,
613                   svn_boolean_t have_write_lock,
614                   apr_pool_t *pool)
615 {
616   apr_hash_index_t *hi;
617   apr_hash_t *children;
618   apr_pool_t *subpool;
619   svn_lock_t *lock;
620
621   /* First, send up any locks in the current digest file. */
622   SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
623
624   SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
625                             children, lock,
626                             have_write_lock, pool));
627
628   /* Now, recurse on this thing's child entries (if any; bail otherwise). */
629   if (! apr_hash_count(children))
630     return SVN_NO_ERROR;
631   subpool = svn_pool_create(pool);
632   for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
633     {
634       const char *digest = svn__apr_hash_index_key(hi);
635       svn_pool_clear(subpool);
636       SVN_ERR(walk_digest_files
637               (fs_path, digest_path_from_digest(fs_path, digest, subpool),
638                walk_digests_func, walk_digests_baton, have_write_lock, subpool));
639     }
640   svn_pool_destroy(subpool);
641   return SVN_NO_ERROR;
642 }
643
644 /* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
645    all locks in and under PATH in FS.
646    HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
647    has the FS write lock. */
648 static svn_error_t *
649 walk_locks(svn_fs_t *fs,
650            const char *digest_path,
651            svn_fs_get_locks_callback_t get_locks_func,
652            void *get_locks_baton,
653            svn_boolean_t have_write_lock,
654            apr_pool_t *pool)
655 {
656   struct walk_locks_baton wlb;
657
658   wlb.get_locks_func = get_locks_func;
659   wlb.get_locks_baton = get_locks_baton;
660   wlb.fs = fs;
661   SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
662                             have_write_lock, pool));
663   return SVN_NO_ERROR;
664 }
665
666
667 /* Utility function:  verify that a lock can be used.  Interesting
668    errors returned from this function:
669
670       SVN_ERR_FS_NO_USER: No username attached to FS.
671       SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
672       SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
673  */
674 static svn_error_t *
675 verify_lock(svn_fs_t *fs,
676             svn_lock_t *lock,
677             apr_pool_t *pool)
678 {
679   if ((! fs->access_ctx) || (! fs->access_ctx->username))
680     return svn_error_createf
681       (SVN_ERR_FS_NO_USER, NULL,
682        _("Cannot verify lock on path '%s'; no username available"),
683        lock->path);
684
685   else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
686     return svn_error_createf
687       (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
688        _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
689        fs->access_ctx->username, lock->path, lock->owner);
690
691   else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
692     return svn_error_createf
693       (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
694        _("Cannot verify lock on path '%s'; no matching lock-token available"),
695        lock->path);
696
697   return SVN_NO_ERROR;
698 }
699
700
701 /* This implements the svn_fs_get_locks_callback_t interface, where
702    BATON is just an svn_fs_t object. */
703 static svn_error_t *
704 get_locks_callback(void *baton,
705                    svn_lock_t *lock,
706                    apr_pool_t *pool)
707 {
708   return verify_lock(baton, lock, pool);
709 }
710
711
712 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
713 svn_error_t *
714 svn_fs_fs__allow_locked_operation(const char *path,
715                                   svn_fs_t *fs,
716                                   svn_boolean_t recurse,
717                                   svn_boolean_t have_write_lock,
718                                   apr_pool_t *pool)
719 {
720   path = svn_fs__canonicalize_abspath(path, pool);
721   if (recurse)
722     {
723       /* Discover all locks at or below the path. */
724       const char *digest_path;
725       SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
726       SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
727                          fs, have_write_lock, pool));
728     }
729   else
730     {
731       /* Discover and verify any lock attached to the path. */
732       svn_lock_t *lock;
733       SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
734       if (lock)
735         SVN_ERR(verify_lock(fs, lock, pool));
736     }
737   return SVN_NO_ERROR;
738 }
739
740 /* Baton used for lock_body below. */
741 struct lock_baton {
742   svn_lock_t **lock_p;
743   svn_fs_t *fs;
744   const char *path;
745   const char *token;
746   const char *comment;
747   svn_boolean_t is_dav_comment;
748   apr_time_t expiration_date;
749   svn_revnum_t current_rev;
750   svn_boolean_t steal_lock;
751   apr_pool_t *pool;
752 };
753
754
755 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
756    type, and assumes that the write lock is held.
757    BATON is a 'struct lock_baton *'. */
758 static svn_error_t *
759 lock_body(void *baton, apr_pool_t *pool)
760 {
761   struct lock_baton *lb = baton;
762   svn_node_kind_t kind;
763   svn_lock_t *existing_lock;
764   svn_lock_t *lock;
765   svn_fs_root_t *root;
766   svn_revnum_t youngest;
767   const char *rev_0_path;
768
769   /* Until we implement directory locks someday, we only allow locks
770      on files or non-existent paths. */
771   /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
772      library dependencies, which are not portable. */
773   SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
774   SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
775   SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
776   if (kind == svn_node_dir)
777     return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
778
779   /* While our locking implementation easily supports the locking of
780      nonexistent paths, we deliberately choose not to allow such madness. */
781   if (kind == svn_node_none)
782     {
783       if (SVN_IS_VALID_REVNUM(lb->current_rev))
784         return svn_error_createf(
785           SVN_ERR_FS_OUT_OF_DATE, NULL,
786           _("Path '%s' doesn't exist in HEAD revision"),
787           lb->path);
788       else
789         return svn_error_createf(
790           SVN_ERR_FS_NOT_FOUND, NULL,
791           _("Path '%s' doesn't exist in HEAD revision"),
792           lb->path);
793     }
794
795   /* We need to have a username attached to the fs. */
796   if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
797     return SVN_FS__ERR_NO_USER(lb->fs);
798
799   /* Is the caller attempting to lock an out-of-date working file? */
800   if (SVN_IS_VALID_REVNUM(lb->current_rev))
801     {
802       svn_revnum_t created_rev;
803       SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
804                                           pool));
805
806       /* SVN_INVALID_REVNUM means the path doesn't exist.  So
807          apparently somebody is trying to lock something in their
808          working copy, but somebody else has deleted the thing
809          from HEAD.  That counts as being 'out of date'. */
810       if (! SVN_IS_VALID_REVNUM(created_rev))
811         return svn_error_createf
812           (SVN_ERR_FS_OUT_OF_DATE, NULL,
813            _("Path '%s' doesn't exist in HEAD revision"), lb->path);
814
815       if (lb->current_rev < created_rev)
816         return svn_error_createf
817           (SVN_ERR_FS_OUT_OF_DATE, NULL,
818            _("Lock failed: newer version of '%s' exists"), lb->path);
819     }
820
821   /* If the caller provided a TOKEN, we *really* need to see
822      if a lock already exists with that token, and if so, verify that
823      the lock's path matches PATH.  Otherwise we run the risk of
824      breaking the 1-to-1 mapping of lock tokens to locked paths. */
825   /* ### TODO:  actually do this check.  This is tough, because the
826      schema doesn't supply a lookup-by-token mechanism. */
827
828   /* Is the path already locked?
829
830      Note that this next function call will automatically ignore any
831      errors about {the path not existing as a key, the path's token
832      not existing as a key, the lock just having been expired}.  And
833      that's totally fine.  Any of these three errors are perfectly
834      acceptable to ignore; it means that the path is now free and
835      clear for locking, because the fsfs funcs just cleared out both
836      of the tables for us.   */
837   SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
838   if (existing_lock)
839     {
840       if (! lb->steal_lock)
841         {
842           /* Sorry, the path is already locked. */
843           return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
844         }
845       else
846         {
847           /* STEAL_LOCK was passed, so fs_username is "stealing" the
848              lock from lock->owner.  Destroy the existing lock. */
849           SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
850         }
851     }
852
853   /* Create our new lock, and add it to the tables.
854      Ensure that the lock is created in the correct pool. */
855   lock = svn_lock_create(lb->pool);
856   if (lb->token)
857     lock->token = apr_pstrdup(lb->pool, lb->token);
858   else
859     SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
860                                            lb->pool));
861   lock->path = apr_pstrdup(lb->pool, lb->path);
862   lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
863   lock->comment = apr_pstrdup(lb->pool, lb->comment);
864   lock->is_dav_comment = lb->is_dav_comment;
865   lock->creation_date = apr_time_now();
866   lock->expiration_date = lb->expiration_date;
867   SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
868   SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
869   *lb->lock_p = lock;
870
871   return SVN_NO_ERROR;
872 }
873
874 /* Baton used for unlock_body below. */
875 struct unlock_baton {
876   svn_fs_t *fs;
877   const char *path;
878   const char *token;
879   svn_boolean_t break_lock;
880 };
881
882 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
883    type, and assumes that the write lock is held.
884    BATON is a 'struct unlock_baton *'. */
885 static svn_error_t *
886 unlock_body(void *baton, apr_pool_t *pool)
887 {
888   struct unlock_baton *ub = baton;
889   svn_lock_t *lock;
890
891   /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
892   SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
893
894   /* Unless breaking the lock, we do some checks. */
895   if (! ub->break_lock)
896     {
897       /* Sanity check:  the incoming token should match lock->token. */
898       if (strcmp(ub->token, lock->token) != 0)
899         return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
900
901       /* There better be a username attached to the fs. */
902       if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
903         return SVN_FS__ERR_NO_USER(ub->fs);
904
905       /* And that username better be the same as the lock's owner. */
906       if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
907         return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
908            ub->fs, ub->fs->access_ctx->username, lock->owner);
909     }
910
911   /* Remove lock and lock token files. */
912   return delete_lock(ub->fs, lock, pool);
913 }
914
915 \f
916 /*** Public API implementations ***/
917
918 svn_error_t *
919 svn_fs_fs__lock(svn_lock_t **lock_p,
920                 svn_fs_t *fs,
921                 const char *path,
922                 const char *token,
923                 const char *comment,
924                 svn_boolean_t is_dav_comment,
925                 apr_time_t expiration_date,
926                 svn_revnum_t current_rev,
927                 svn_boolean_t steal_lock,
928                 apr_pool_t *pool)
929 {
930   struct lock_baton lb;
931
932   SVN_ERR(svn_fs__check_fs(fs, TRUE));
933   path = svn_fs__canonicalize_abspath(path, pool);
934
935   lb.lock_p = lock_p;
936   lb.fs = fs;
937   lb.path = path;
938   lb.token = token;
939   lb.comment = comment;
940   lb.is_dav_comment = is_dav_comment;
941   lb.expiration_date = expiration_date;
942   lb.current_rev = current_rev;
943   lb.steal_lock = steal_lock;
944   lb.pool = pool;
945
946   return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
947 }
948
949
950 svn_error_t *
951 svn_fs_fs__generate_lock_token(const char **token,
952                                svn_fs_t *fs,
953                                apr_pool_t *pool)
954 {
955   SVN_ERR(svn_fs__check_fs(fs, TRUE));
956
957   /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
958      want to use the fs UUID + some incremented number?  For now, we
959      generate a URI that matches the DAV RFC.  We could change this to
960      some other URI scheme someday, if we wish. */
961   *token = apr_pstrcat(pool, "opaquelocktoken:",
962                        svn_uuid_generate(pool), (char *)NULL);
963   return SVN_NO_ERROR;
964 }
965
966
967 svn_error_t *
968 svn_fs_fs__unlock(svn_fs_t *fs,
969                   const char *path,
970                   const char *token,
971                   svn_boolean_t break_lock,
972                   apr_pool_t *pool)
973 {
974   struct unlock_baton ub;
975
976   SVN_ERR(svn_fs__check_fs(fs, TRUE));
977   path = svn_fs__canonicalize_abspath(path, pool);
978
979   ub.fs = fs;
980   ub.path = path;
981   ub.token = token;
982   ub.break_lock = break_lock;
983
984   return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
985 }
986
987
988 svn_error_t *
989 svn_fs_fs__get_lock(svn_lock_t **lock_p,
990                     svn_fs_t *fs,
991                     const char *path,
992                     apr_pool_t *pool)
993 {
994   SVN_ERR(svn_fs__check_fs(fs, TRUE));
995   path = svn_fs__canonicalize_abspath(path, pool);
996   return get_lock_helper(fs, lock_p, path, FALSE, pool);
997 }
998
999
1000 /* Baton for get_locks_filter_func(). */
1001 typedef struct get_locks_filter_baton_t
1002 {
1003   const char *path;
1004   svn_depth_t requested_depth;
1005   svn_fs_get_locks_callback_t get_locks_func;
1006   void *get_locks_baton;
1007
1008 } get_locks_filter_baton_t;
1009
1010
1011 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
1012    which filters out locks on paths that aren't within
1013    BATON->requested_depth of BATON->path before called
1014    BATON->get_locks_func() with BATON->get_locks_baton.
1015
1016    NOTE: See issue #3660 for details about how the FSFS lock
1017    management code is inconsistent.  Until that inconsistency is
1018    resolved, we take this filtering approach rather than honoring
1019    depth requests closer to the crawling code.  In other words, once
1020    we decide how to resolve issue #3660, there might be a more
1021    performant way to honor the depth passed to svn_fs_fs__get_locks().  */
1022 static svn_error_t *
1023 get_locks_filter_func(void *baton,
1024                       svn_lock_t *lock,
1025                       apr_pool_t *pool)
1026 {
1027   get_locks_filter_baton_t *b = baton;
1028
1029   /* Filter out unwanted paths.  Since Subversion only allows
1030      locks on files, we can treat depth=immediates the same as
1031      depth=files for filtering purposes.  Meaning, we'll keep
1032      this lock if:
1033
1034      a) its path is the very path we queried, or
1035      b) we've asked for a fully recursive answer, or
1036      c) we've asked for depth=files or depth=immediates, and this
1037         lock is on an immediate child of our query path.
1038   */
1039   if ((strcmp(b->path, lock->path) == 0)
1040       || (b->requested_depth == svn_depth_infinity))
1041     {
1042       SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1043     }
1044   else if ((b->requested_depth == svn_depth_files) ||
1045            (b->requested_depth == svn_depth_immediates))
1046     {
1047       const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1048       if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1049         SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1050     }
1051
1052   return SVN_NO_ERROR;
1053 }
1054
1055 svn_error_t *
1056 svn_fs_fs__get_locks(svn_fs_t *fs,
1057                      const char *path,
1058                      svn_depth_t depth,
1059                      svn_fs_get_locks_callback_t get_locks_func,
1060                      void *get_locks_baton,
1061                      apr_pool_t *pool)
1062 {
1063   const char *digest_path;
1064   get_locks_filter_baton_t glfb;
1065
1066   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1067   path = svn_fs__canonicalize_abspath(path, pool);
1068
1069   glfb.path = path;
1070   glfb.requested_depth = depth;
1071   glfb.get_locks_func = get_locks_func;
1072   glfb.get_locks_baton = get_locks_baton;
1073
1074   /* Get the top digest path in our tree of interest, and then walk it. */
1075   SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1076   SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1077                      FALSE, pool));
1078   return SVN_NO_ERROR;
1079 }