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 #include "svn_pools.h"
24 #include "svn_error.h"
25 #include "svn_dirent_uri.h"
26 #include "svn_path.h"
27 #include "svn_fs.h"
28 #include "svn_hash.h"
29 #include "svn_time.h"
30 #include "svn_utf.h"
31 
32 #include <apr_uuid.h>
33 #include <apr_file_io.h>
34 #include <apr_file_info.h>
35 
36 #include "lock.h"
37 #include "tree.h"
38 #include "fs_x.h"
39 #include "transaction.h"
40 #include "util.h"
41 #include "../libsvn_fs/fs-loader.h"
42 
43 #include "private/svn_fs_util.h"
44 #include "private/svn_fspath.h"
45 #include "private/svn_sorts_private.h"
46 #include "svn_private_config.h"
47 
48 /* Names of hash keys used to store a lock for writing to disk. */
49 #define PATH_KEY "path"
50 #define TOKEN_KEY "token"
51 #define OWNER_KEY "owner"
52 #define CREATION_DATE_KEY "creation_date"
53 #define EXPIRATION_DATE_KEY "expiration_date"
54 #define COMMENT_KEY "comment"
55 #define IS_DAV_COMMENT_KEY "is_dav_comment"
56 #define CHILDREN_KEY "children"
57 
58 /* Number of characters from the head of a digest file name used to
59    calculate a subdirectory in which to drop that file. */
60 #define DIGEST_SUBDIR_LEN 3
61 
62 
63 
64 /*** Generic helper functions. ***/
65 
66 /* Set *DIGEST to the MD5 hash of STR. */
67 static svn_error_t *
make_digest(const char ** digest,const char * str,apr_pool_t * pool)68 make_digest(const char **digest,
69             const char *str,
70             apr_pool_t *pool)
71 {
72   svn_checksum_t *checksum;
73 
74   SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
75 
76   *digest = svn_checksum_to_cstring_display(checksum, pool);
77   return SVN_NO_ERROR;
78 }
79 
80 
81 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
82    if unknown) to an svn_string_t-ized version of VALUE (whose size is
83    VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
84    will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
85    is NULL, this function will do nothing. */
86 static void
hash_store(apr_hash_t * hash,const char * key,apr_ssize_t key_len,const char * value,apr_ssize_t value_len,apr_pool_t * pool)87 hash_store(apr_hash_t *hash,
88            const char *key,
89            apr_ssize_t key_len,
90            const char *value,
91            apr_ssize_t value_len,
92            apr_pool_t *pool)
93 {
94   if (! (key && value))
95     return;
96   if (value_len == APR_HASH_KEY_STRING)
97     value_len = strlen(value);
98   apr_hash_set(hash, key, key_len,
99                svn_string_ncreate(value, value_len, pool));
100 }
101 
102 
103 /* Fetch the value of KEY from HASH, returning only the cstring data
104    of that value (if it exists). */
105 static const char *
hash_fetch(apr_hash_t * hash,const char * key)106 hash_fetch(apr_hash_t *hash,
107            const char *key)
108 {
109   svn_string_t *str = svn_hash_gets(hash, key);
110   return str ? str->data : NULL;
111 }
112 
113 
114 /* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt.  */
115 static svn_error_t *
err_corrupt_lockfile(const char * fs_path,const char * path)116 err_corrupt_lockfile(const char *fs_path,
117                      const char *path)
118 {
119   return
120     svn_error_createf(
121      SVN_ERR_FS_CORRUPT, 0,
122      _("Corrupt lockfile for path '%s' in filesystem '%s'"),
123      path, fs_path);
124 }
125 
126 
127 /*** Digest file handling functions. ***/
128 
129 /* Return the path of the lock/entries file for which DIGEST is the
130    hashed repository relative path. */
131 static const char *
digest_path_from_digest(const char * fs_path,const char * digest,apr_pool_t * pool)132 digest_path_from_digest(const char *fs_path,
133                         const char *digest,
134                         apr_pool_t *pool)
135 {
136   return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
137                               apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
138                               digest, SVN_VA_NULL);
139 }
140 
141 
142 /* Set *DIGEST_PATH to the path to the lock/entries digest file associate
143    with PATH, where PATH is the path to the lock file or lock entries file
144    in FS. */
145 static svn_error_t *
digest_path_from_path(const char ** digest_path,const char * fs_path,const char * path,apr_pool_t * pool)146 digest_path_from_path(const char **digest_path,
147                       const char *fs_path,
148                       const char *path,
149                       apr_pool_t *pool)
150 {
151   const char *digest;
152   SVN_ERR(make_digest(&digest, path, pool));
153   *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
154                                       apr_pstrmemdup(pool, digest,
155                                                      DIGEST_SUBDIR_LEN),
156                                       digest, SVN_VA_NULL);
157   return SVN_NO_ERROR;
158 }
159 
160 
161 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
162    empty, if the versioned path in FS represented by DIGEST_PATH has
163    no children) and LOCK (which may be NULL if that versioned path is
164    lock itself locked).  Set the permissions of DIGEST_PATH to those of
165    PERMS_REFERENCE.  Use POOL for temporary allocations.
166  */
167 static svn_error_t *
write_digest_file(apr_hash_t * children,svn_lock_t * lock,const char * fs_path,const char * digest_path,const char * perms_reference,apr_pool_t * scratch_pool)168 write_digest_file(apr_hash_t *children,
169                   svn_lock_t *lock,
170                   const char *fs_path,
171                   const char *digest_path,
172                   const char *perms_reference,
173                   apr_pool_t *scratch_pool)
174 {
175   svn_error_t *err = SVN_NO_ERROR;
176   svn_stream_t *stream;
177   apr_hash_index_t *hi;
178   apr_hash_t *hash = apr_hash_make(scratch_pool);
179   const char *tmp_path;
180 
181   SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
182                                                       scratch_pool),
183                                       fs_path, scratch_pool));
184   SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path,
185                                                          scratch_pool),
186                                       fs_path, scratch_pool));
187 
188   if (lock)
189     {
190       const char *creation_date = NULL, *expiration_date = NULL;
191       if (lock->creation_date)
192         creation_date = svn_time_to_cstring(lock->creation_date,
193                                             scratch_pool);
194       if (lock->expiration_date)
195         expiration_date = svn_time_to_cstring(lock->expiration_date,
196                                               scratch_pool);
197 
198       hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
199                  lock->path, APR_HASH_KEY_STRING, scratch_pool);
200       hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
201                  lock->token, APR_HASH_KEY_STRING, scratch_pool);
202       hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
203                  lock->owner, APR_HASH_KEY_STRING, scratch_pool);
204       hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
205                  lock->comment, APR_HASH_KEY_STRING, scratch_pool);
206       hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
207                  lock->is_dav_comment ? "1" : "0", 1, scratch_pool);
208       hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
209                  creation_date, APR_HASH_KEY_STRING, scratch_pool);
210       hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
211                  expiration_date, APR_HASH_KEY_STRING, scratch_pool);
212     }
213   if (apr_hash_count(children))
214     {
215       svn_stringbuf_t *children_list
216         = svn_stringbuf_create_empty(scratch_pool);
217       for (hi = apr_hash_first(scratch_pool, children);
218            hi;
219            hi = apr_hash_next(hi))
220         {
221           svn_stringbuf_appendbytes(children_list,
222                                     apr_hash_this_key(hi),
223                                     apr_hash_this_key_len(hi));
224           svn_stringbuf_appendbyte(children_list, '\n');
225         }
226       hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
227                  children_list->data, children_list->len, scratch_pool);
228     }
229 
230   SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
231                                  svn_dirent_dirname(digest_path,
232                                                     scratch_pool),
233                                  svn_io_file_del_none, scratch_pool,
234                                  scratch_pool));
235   if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR,
236                              scratch_pool)))
237     {
238       err = svn_error_compose_create(err, svn_stream_close(stream));
239       return svn_error_createf(err->apr_err,
240                                err,
241                                _("Cannot write lock/entries hashfile '%s'"),
242                                svn_dirent_local_style(tmp_path,
243                                                       scratch_pool));
244     }
245 
246   SVN_ERR(svn_stream_close(stream));
247   SVN_ERR(svn_io_file_rename2(tmp_path, digest_path, FALSE, scratch_pool));
248   SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool));
249   return SVN_NO_ERROR;
250 }
251 
252 
253 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
254    file (if it exists, and if *LOCK_P is non-NULL) and the hash of
255    CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL).  Use POOL
256    for all allocations.  */
257 static svn_error_t *
read_digest_file(apr_hash_t ** children_p,svn_lock_t ** lock_p,const char * fs_path,const char * digest_path,apr_pool_t * pool)258 read_digest_file(apr_hash_t **children_p,
259                  svn_lock_t **lock_p,
260                  const char *fs_path,
261                  const char *digest_path,
262                  apr_pool_t *pool)
263 {
264   svn_error_t *err = SVN_NO_ERROR;
265   svn_lock_t *lock;
266   apr_hash_t *hash;
267   svn_stream_t *stream;
268   const char *val;
269   svn_node_kind_t kind;
270 
271   if (lock_p)
272     *lock_p = NULL;
273   if (children_p)
274     *children_p = apr_hash_make(pool);
275 
276   SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
277   if (kind == svn_node_none)
278     return SVN_NO_ERROR;
279 
280   /* If our caller doesn't care about anything but the presence of the
281      file... whatever. */
282   if (kind == svn_node_file && !lock_p && !children_p)
283     return SVN_NO_ERROR;
284 
285   SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
286 
287   hash = apr_hash_make(pool);
288   if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
289     {
290       err = svn_error_compose_create(err, svn_stream_close(stream));
291       return svn_error_createf(err->apr_err,
292                                err,
293                                _("Can't parse lock/entries hashfile '%s'"),
294                                svn_dirent_local_style(digest_path, pool));
295     }
296   SVN_ERR(svn_stream_close(stream));
297 
298   /* If our caller cares, see if we have a lock path in our hash. If
299      so, we'll assume we have a lock here. */
300   val = hash_fetch(hash, PATH_KEY);
301   if (val && lock_p)
302     {
303       const char *path = val;
304 
305       /* Create our lock and load it up. */
306       lock = svn_lock_create(pool);
307       lock->path = path;
308 
309       if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
310         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
311 
312       if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
313         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
314 
315       if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
316         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
317       lock->is_dav_comment = (val[0] == '1');
318 
319       if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
320         return svn_error_trace(err_corrupt_lockfile(fs_path, path));
321       SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
322 
323       if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
324         SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
325 
326       lock->comment = hash_fetch(hash, COMMENT_KEY);
327 
328       *lock_p = lock;
329     }
330 
331   /* If our caller cares, see if we have any children for this path. */
332   val = hash_fetch(hash, CHILDREN_KEY);
333   if (val && children_p)
334     {
335       apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
336       int i;
337 
338       for (i = 0; i < kiddos->nelts; i++)
339         {
340           svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
341                         (void *)1);
342         }
343     }
344   return SVN_NO_ERROR;
345 }
346 
347 
348 
349 /*** Lock helper functions (path here are still FS paths, not on-disk
350      schema-supporting paths) ***/
351 
352 
353 /* Write LOCK in FS to the actual OS filesystem.
354 
355    Use PERMS_REFERENCE for the permissions of any digest files.
356  */
357 static svn_error_t *
set_lock(const char * fs_path,svn_lock_t * lock,const char * perms_reference,apr_pool_t * scratch_pool)358 set_lock(const char *fs_path,
359          svn_lock_t *lock,
360          const char *perms_reference,
361          apr_pool_t *scratch_pool)
362 {
363   const char *digest_path;
364   apr_hash_t *children;
365 
366   SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path,
367                                 scratch_pool));
368 
369   /* We could get away without reading the file as children should
370      always come back empty. */
371   SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path,
372                            scratch_pool));
373 
374   SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
375                             perms_reference, scratch_pool));
376 
377   return SVN_NO_ERROR;
378 }
379 
380 static svn_error_t *
delete_lock(const char * fs_path,const char * path,apr_pool_t * scratch_pool)381 delete_lock(const char *fs_path,
382             const char *path,
383             apr_pool_t *scratch_pool)
384 {
385   const char *digest_path;
386 
387   SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool));
388 
389   SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool));
390 
391   return SVN_NO_ERROR;
392 }
393 
394 static svn_error_t *
add_to_digest(const char * fs_path,apr_array_header_t * paths,const char * index_path,const char * perms_reference,apr_pool_t * scratch_pool)395 add_to_digest(const char *fs_path,
396               apr_array_header_t *paths,
397               const char *index_path,
398               const char *perms_reference,
399               apr_pool_t *scratch_pool)
400 {
401   const char *index_digest_path;
402   apr_hash_t *children;
403   svn_lock_t *lock;
404   int i;
405   unsigned int original_count;
406 
407   SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
408                                 scratch_pool));
409   SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
410                            scratch_pool));
411 
412   original_count = apr_hash_count(children);
413 
414   for (i = 0; i < paths->nelts; ++i)
415     {
416       const char *path = APR_ARRAY_IDX(paths, i, const char *);
417       const char *digest_path, *digest_file;
418 
419       SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
420                                     scratch_pool));
421       digest_file = svn_dirent_basename(digest_path, NULL);
422       svn_hash_sets(children, digest_file, (void *)1);
423     }
424 
425   if (apr_hash_count(children) != original_count)
426     SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
427                               perms_reference, scratch_pool));
428 
429   return SVN_NO_ERROR;
430 }
431 
432 static svn_error_t *
delete_from_digest(const char * fs_path,apr_array_header_t * paths,const char * index_path,const char * perms_reference,apr_pool_t * scratch_pool)433 delete_from_digest(const char *fs_path,
434                    apr_array_header_t *paths,
435                    const char *index_path,
436                    const char *perms_reference,
437                    apr_pool_t *scratch_pool)
438 {
439   const char *index_digest_path;
440   apr_hash_t *children;
441   svn_lock_t *lock;
442   int i;
443 
444   SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
445                                 scratch_pool));
446   SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
447                            scratch_pool));
448 
449   for (i = 0; i < paths->nelts; ++i)
450     {
451       const char *path = APR_ARRAY_IDX(paths, i, const char *);
452       const char *digest_path, *digest_file;
453 
454       SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
455                                     scratch_pool));
456       digest_file = svn_dirent_basename(digest_path, NULL);
457       svn_hash_sets(children, digest_file, NULL);
458     }
459 
460   if (apr_hash_count(children) || lock)
461     SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
462                               perms_reference, scratch_pool));
463   else
464     SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool));
465 
466   return SVN_NO_ERROR;
467 }
468 
469 static svn_error_t *
470 unlock_single(svn_fs_t *fs,
471               svn_lock_t *lock,
472               apr_pool_t *pool);
473 
474 /* Check if LOCK has been already expired. */
lock_expired(const svn_lock_t * lock)475 static svn_boolean_t lock_expired(const svn_lock_t *lock)
476 {
477   return lock->expiration_date && (apr_time_now() > lock->expiration_date);
478 }
479 
480 /* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
481    TRUE if the caller (or one of its callers) has taken out the
482    repository-wide write lock, FALSE otherwise.  If MUST_EXIST is
483    not set, the function will simply return NULL in *LOCK_P instead
484    of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
485    was not found (much faster).  Use POOL for allocations. */
486 static svn_error_t *
get_lock(svn_lock_t ** lock_p,svn_fs_t * fs,const char * path,svn_boolean_t have_write_lock,svn_boolean_t must_exist,apr_pool_t * pool)487 get_lock(svn_lock_t **lock_p,
488          svn_fs_t *fs,
489          const char *path,
490          svn_boolean_t have_write_lock,
491          svn_boolean_t must_exist,
492          apr_pool_t *pool)
493 {
494   svn_lock_t *lock = NULL;
495   const char *digest_path;
496   svn_node_kind_t kind;
497 
498   SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
499   SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
500 
501   *lock_p = NULL;
502   if (kind != svn_node_none)
503     SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
504 
505   if (! lock)
506     return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
507 
508   /* Don't return an expired lock. */
509   if (lock_expired(lock))
510     {
511       /* Only remove the lock if we have the write lock.
512          Read operations shouldn't change the filesystem. */
513       if (have_write_lock)
514         SVN_ERR(unlock_single(fs, lock, pool));
515       return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
516     }
517 
518   *lock_p = lock;
519   return SVN_NO_ERROR;
520 }
521 
522 
523 /* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
524    TRUE if the caller (or one of its callers) has taken out the
525    repository-wide write lock, FALSE otherwise.  Use POOL for
526    allocations. */
527 static svn_error_t *
get_lock_helper(svn_fs_t * fs,svn_lock_t ** lock_p,const char * path,svn_boolean_t have_write_lock,apr_pool_t * pool)528 get_lock_helper(svn_fs_t *fs,
529                 svn_lock_t **lock_p,
530                 const char *path,
531                 svn_boolean_t have_write_lock,
532                 apr_pool_t *pool)
533 {
534   svn_lock_t *lock;
535   svn_error_t *err;
536 
537   err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
538 
539   /* We've deliberately decided that this function doesn't tell the
540      caller *why* the lock is unavailable.  */
541   if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
542               || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
543     {
544       svn_error_clear(err);
545       *lock_p = NULL;
546       return SVN_NO_ERROR;
547     }
548   else
549     SVN_ERR(err);
550 
551   *lock_p = lock;
552   return SVN_NO_ERROR;
553 }
554 
555 
556 /* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
557    all locks in and under PATH in FS.
558    HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
559    has the FS write lock. */
560 static svn_error_t *
walk_locks(svn_fs_t * fs,const char * digest_path,svn_fs_get_locks_callback_t get_locks_func,void * get_locks_baton,svn_boolean_t have_write_lock,apr_pool_t * pool)561 walk_locks(svn_fs_t *fs,
562            const char *digest_path,
563            svn_fs_get_locks_callback_t get_locks_func,
564            void *get_locks_baton,
565            svn_boolean_t have_write_lock,
566            apr_pool_t *pool)
567 {
568   apr_hash_index_t *hi;
569   apr_hash_t *children;
570   apr_pool_t *subpool;
571   svn_lock_t *lock;
572 
573   /* First, send up any locks in the current digest file. */
574   SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
575 
576   if (lock && lock_expired(lock))
577     {
578       /* Only remove the lock if we have the write lock.
579          Read operations shouldn't change the filesystem. */
580       if (have_write_lock)
581         SVN_ERR(unlock_single(fs, lock, pool));
582     }
583   else if (lock)
584     {
585       SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
586     }
587 
588   /* Now, report all the child entries (if any; bail otherwise). */
589   if (! apr_hash_count(children))
590     return SVN_NO_ERROR;
591   subpool = svn_pool_create(pool);
592   for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
593     {
594       const char *digest = apr_hash_this_key(hi);
595       svn_pool_clear(subpool);
596 
597       SVN_ERR(read_digest_file
598               (NULL, &lock, fs->path,
599                digest_path_from_digest(fs->path, digest, subpool), subpool));
600 
601       if (lock && lock_expired(lock))
602         {
603           /* Only remove the lock if we have the write lock.
604              Read operations shouldn't change the filesystem. */
605           if (have_write_lock)
606             SVN_ERR(unlock_single(fs, lock, pool));
607         }
608       else if (lock)
609         {
610           SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
611         }
612     }
613   svn_pool_destroy(subpool);
614   return SVN_NO_ERROR;
615 }
616 
617 /* Utility function:  verify that a lock can be used.  Interesting
618    errors returned from this function:
619 
620       SVN_ERR_FS_NO_USER: No username attached to FS.
621       SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
622       SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
623  */
624 static svn_error_t *
verify_lock(svn_fs_t * fs,svn_lock_t * lock)625 verify_lock(svn_fs_t *fs,
626             svn_lock_t *lock)
627 {
628   if ((! fs->access_ctx) || (! fs->access_ctx->username))
629     return svn_error_createf
630       (SVN_ERR_FS_NO_USER, NULL,
631        _("Cannot verify lock on path '%s'; no username available"),
632        lock->path);
633 
634   else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
635     return svn_error_createf
636       (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
637        _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
638        fs->access_ctx->username, lock->path, lock->owner);
639 
640   else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
641     return svn_error_createf
642       (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
643        _("Cannot verify lock on path '%s'; no matching lock-token available"),
644        lock->path);
645 
646   return SVN_NO_ERROR;
647 }
648 
649 
650 /* This implements the svn_fs_get_locks_callback_t interface, where
651    BATON is just an svn_fs_t object. */
652 static svn_error_t *
get_locks_callback(void * baton,svn_lock_t * lock,apr_pool_t * pool)653 get_locks_callback(void *baton,
654                    svn_lock_t *lock,
655                    apr_pool_t *pool)
656 {
657   return verify_lock(baton, lock);
658 }
659 
660 
661 /* The main routine for lock enforcement, used throughout libsvn_fs_x. */
662 svn_error_t *
svn_fs_x__allow_locked_operation(const char * path,svn_fs_t * fs,svn_boolean_t recurse,svn_boolean_t have_write_lock,apr_pool_t * scratch_pool)663 svn_fs_x__allow_locked_operation(const char *path,
664                                  svn_fs_t *fs,
665                                  svn_boolean_t recurse,
666                                  svn_boolean_t have_write_lock,
667                                  apr_pool_t *scratch_pool)
668 {
669   path = svn_fs__canonicalize_abspath(path, scratch_pool);
670   if (recurse)
671     {
672       /* Discover all locks at or below the path. */
673       const char *digest_path;
674       SVN_ERR(digest_path_from_path(&digest_path, fs->path, path,
675                                     scratch_pool));
676       SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
677                          fs, have_write_lock, scratch_pool));
678     }
679   else
680     {
681       /* Discover and verify any lock attached to the path. */
682       svn_lock_t *lock;
683       SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock,
684                               scratch_pool));
685       if (lock)
686         SVN_ERR(verify_lock(fs, lock));
687     }
688   return SVN_NO_ERROR;
689 }
690 
691 /* Helper function called from the lock and unlock code.
692    UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
693    arrays of child paths.  For all of the parent paths of PATH this function
694    adds PATH to the corresponding array of child paths. */
695 static void
schedule_index_update(apr_hash_t * updates,const char * path,apr_pool_t * scratch_pool)696 schedule_index_update(apr_hash_t *updates,
697                       const char *path,
698                       apr_pool_t *scratch_pool)
699 {
700   apr_pool_t *hashpool = apr_hash_pool_get(updates);
701   const char *parent_path = path;
702 
703   while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
704     {
705       apr_array_header_t *children;
706 
707       parent_path = svn_fspath__dirname(parent_path, scratch_pool);
708       children = svn_hash_gets(updates, parent_path);
709 
710       if (! children)
711         {
712           children = apr_array_make(hashpool, 8, sizeof(const char *));
713           svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
714         }
715 
716       APR_ARRAY_PUSH(children, const char *) = path;
717     }
718 }
719 
720 /* The effective arguments for lock_body() below. */
721 typedef struct lock_baton_t {
722   svn_fs_t *fs;
723   apr_array_header_t *targets;
724   apr_array_header_t *infos;
725   const char *comment;
726   svn_boolean_t is_dav_comment;
727   apr_time_t expiration_date;
728   svn_boolean_t steal_lock;
729   apr_pool_t *result_pool;
730 } lock_baton_t;
731 
732 static svn_error_t *
check_lock(svn_error_t ** fs_err,const char * path,const svn_fs_lock_target_t * target,lock_baton_t * lb,svn_fs_root_t * root,svn_revnum_t youngest_rev,apr_pool_t * pool)733 check_lock(svn_error_t **fs_err,
734            const char *path,
735            const svn_fs_lock_target_t *target,
736            lock_baton_t *lb,
737            svn_fs_root_t *root,
738            svn_revnum_t youngest_rev,
739            apr_pool_t *pool)
740 {
741   svn_node_kind_t kind;
742   svn_lock_t *existing_lock;
743 
744   *fs_err = SVN_NO_ERROR;
745 
746   SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool));
747   if (kind == svn_node_dir)
748     {
749       *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
750       return SVN_NO_ERROR;
751     }
752 
753   /* While our locking implementation easily supports the locking of
754      nonexistent paths, we deliberately choose not to allow such madness. */
755   if (kind == svn_node_none)
756     {
757       if (SVN_IS_VALID_REVNUM(target->current_rev))
758         *fs_err = svn_error_createf(
759           SVN_ERR_FS_OUT_OF_DATE, NULL,
760           _("Path '%s' doesn't exist in HEAD revision"),
761           path);
762       else
763         *fs_err = svn_error_createf(
764           SVN_ERR_FS_NOT_FOUND, NULL,
765           _("Path '%s' doesn't exist in HEAD revision"),
766           path);
767 
768       return SVN_NO_ERROR;
769     }
770 
771   /* Is the caller attempting to lock an out-of-date working file? */
772   if (SVN_IS_VALID_REVNUM(target->current_rev))
773     {
774       svn_revnum_t created_rev;
775 
776       if (target->current_rev > youngest_rev)
777         {
778           *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
779                                       _("No such revision %ld"),
780                                       target->current_rev);
781           return SVN_NO_ERROR;
782         }
783 
784       SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
785                                          pool));
786 
787       /* SVN_INVALID_REVNUM means the path doesn't exist.  So
788          apparently somebody is trying to lock something in their
789          working copy, but somebody else has deleted the thing
790          from HEAD.  That counts as being 'out of date'. */
791       if (! SVN_IS_VALID_REVNUM(created_rev))
792         {
793           *fs_err = svn_error_createf
794             (SVN_ERR_FS_OUT_OF_DATE, NULL,
795              _("Path '%s' doesn't exist in HEAD revision"), path);
796 
797           return SVN_NO_ERROR;
798         }
799 
800       if (target->current_rev < created_rev)
801         {
802           *fs_err = svn_error_createf
803             (SVN_ERR_FS_OUT_OF_DATE, NULL,
804              _("Lock failed: newer version of '%s' exists"), path);
805 
806           return SVN_NO_ERROR;
807         }
808     }
809 
810   /* If the caller provided a TOKEN, we *really* need to see
811      if a lock already exists with that token, and if so, verify that
812      the lock's path matches PATH.  Otherwise we run the risk of
813      breaking the 1-to-1 mapping of lock tokens to locked paths. */
814   /* ### TODO:  actually do this check.  This is tough, because the
815      schema doesn't supply a lookup-by-token mechanism. */
816 
817   /* Is the path already locked?
818 
819      Note that this next function call will automatically ignore any
820      errors about {the path not existing as a key, the path's token
821      not existing as a key, the lock just having been expired}.  And
822      that's totally fine.  Any of these three errors are perfectly
823      acceptable to ignore; it means that the path is now free and
824      clear for locking, because the fsx funcs just cleared out both
825      of the tables for us.   */
826   SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
827   if (existing_lock)
828     {
829       if (! lb->steal_lock)
830         {
831           /* Sorry, the path is already locked. */
832           *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
833           return SVN_NO_ERROR;
834         }
835     }
836 
837   return SVN_NO_ERROR;
838 }
839 
840 typedef struct lock_info_t {
841   const char *path;
842   svn_lock_t *lock;
843   svn_error_t *fs_err;
844 } lock_info_t;
845 
846 /* The body of svn_fs_x__lock(), which see.
847 
848    BATON is a 'lock_baton_t *' holding the effective arguments.
849    BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
850    path, mapping canonical path to 'svn_fs_lock_target_t'.  Set
851    BATON->infos to an array of 'lock_info_t' holding the results.  For
852    the other arguments, see svn_fs_lock_many().
853 
854    This implements the svn_fs_x__with_write_lock() 'body' callback
855    type, and assumes that the write lock is held.
856  */
857 static svn_error_t *
lock_body(void * baton,apr_pool_t * pool)858 lock_body(void *baton,
859           apr_pool_t *pool)
860 {
861   lock_baton_t *lb = baton;
862   svn_fs_root_t *root;
863   svn_revnum_t youngest;
864   const char *rev_0_path;
865   int i;
866   apr_hash_t *index_updates = apr_hash_make(pool);
867   apr_hash_index_t *hi;
868   apr_pool_t *iterpool = svn_pool_create(pool);
869 
870   /* Until we implement directory locks someday, we only allow locks
871      on files. */
872   /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
873      library dependencies, which are not portable. */
874   SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
875   SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
876 
877   for (i = 0; i < lb->targets->nelts; ++i)
878     {
879       const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
880                                                     svn_sort__item_t);
881       lock_info_t info;
882 
883       svn_pool_clear(iterpool);
884 
885       info.path = item->key;
886       info.lock = NULL;
887       info.fs_err = SVN_NO_ERROR;
888 
889       SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
890                          youngest, iterpool));
891 
892       /* If no error occurred while pre-checking, schedule the index updates for
893          this path. */
894       if (!info.fs_err)
895         schedule_index_update(index_updates, info.path, iterpool);
896 
897       APR_ARRAY_PUSH(lb->infos, lock_info_t) = info;
898     }
899 
900   rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
901 
902   /* We apply the scheduled index updates before writing the actual locks.
903 
904      Writing indices before locks is correct: if interrupted it leaves
905      indices without locks rather than locks without indices.  An
906      index without a lock is consistent in that it always shows up as
907      unlocked in svn_fs_x__allow_locked_operation.  A lock without an
908      index is inconsistent, svn_fs_x__allow_locked_operation will
909      show locked on the file but unlocked on the parent. */
910 
911   for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
912     {
913       const char *path = apr_hash_this_key(hi);
914       apr_array_header_t *children = apr_hash_this_val(hi);
915 
916       svn_pool_clear(iterpool);
917       SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
918                             iterpool));
919     }
920 
921   for (i = 0; i < lb->infos->nelts; ++i)
922     {
923       struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
924                                                 struct lock_info_t);
925       svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
926       svn_fs_lock_target_t *target = item->value;
927 
928       svn_pool_clear(iterpool);
929 
930       if (! info->fs_err)
931         {
932           info->lock = svn_lock_create(lb->result_pool);
933           if (target->token)
934             info->lock->token = apr_pstrdup(lb->result_pool, target->token);
935           else
936             SVN_ERR(svn_fs_x__generate_lock_token(&(info->lock->token), lb->fs,
937                                                   lb->result_pool));
938 
939           /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
940              of svn_fspath__canonicalize() (see svn_fs_x__lock()). */
941           info->lock->path = info->path;
942           info->lock->owner = apr_pstrdup(lb->result_pool,
943                                           lb->fs->access_ctx->username);
944           info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
945           info->lock->is_dav_comment = lb->is_dav_comment;
946           info->lock->creation_date = apr_time_now();
947           info->lock->expiration_date = lb->expiration_date;
948 
949           info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
950                                   iterpool);
951         }
952     }
953 
954   svn_pool_destroy(iterpool);
955   return SVN_NO_ERROR;
956 }
957 
958 /* The effective arguments for unlock_body() below. */
959 typedef struct unlock_baton_t {
960   svn_fs_t *fs;
961   apr_array_header_t *targets;
962   apr_array_header_t *infos;
963   /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
964   svn_boolean_t skip_check;
965   svn_boolean_t break_lock;
966   apr_pool_t *result_pool;
967 } unlock_baton_t;
968 
969 static svn_error_t *
check_unlock(svn_error_t ** fs_err,const char * path,const char * token,unlock_baton_t * ub,svn_fs_root_t * root,apr_pool_t * pool)970 check_unlock(svn_error_t **fs_err,
971              const char *path,
972              const char *token,
973              unlock_baton_t *ub,
974              svn_fs_root_t *root,
975              apr_pool_t *pool)
976 {
977   svn_lock_t *lock;
978 
979   *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
980   if (!*fs_err && !ub->break_lock)
981     {
982       if (strcmp(token, lock->token) != 0)
983         *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
984       else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
985         *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
986                                                   ub->fs->access_ctx->username,
987                                                   lock->owner);
988     }
989 
990   return SVN_NO_ERROR;
991 }
992 
993 typedef struct unlock_info_t {
994   const char *path;
995   svn_error_t *fs_err;
996   svn_boolean_t done;
997 } unlock_info_t;
998 
999 /* The body of svn_fs_x__unlock(), which see.
1000 
1001    BATON is a 'unlock_baton_t *' holding the effective arguments.
1002    BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
1003    path, mapping canonical path to (const char *) token.  Set
1004    BATON->infos to an array of 'unlock_info_t' results.  For the other
1005    arguments, see svn_fs_unlock_many().
1006 
1007    This implements the svn_fs_x__with_write_lock() 'body' callback
1008    type, and assumes that the write lock is held.
1009  */
1010 static svn_error_t *
unlock_body(void * baton,apr_pool_t * pool)1011 unlock_body(void *baton,
1012             apr_pool_t *pool)
1013 {
1014   unlock_baton_t *ub = baton;
1015   svn_fs_root_t *root;
1016   svn_revnum_t youngest;
1017   const char *rev_0_path;
1018   int i;
1019   apr_hash_t *indices_updates = apr_hash_make(pool);
1020   apr_hash_index_t *hi;
1021   apr_pool_t *iterpool = svn_pool_create(pool);
1022 
1023   SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1024   SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1025 
1026   for (i = 0; i < ub->targets->nelts; ++i)
1027     {
1028       const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1029                                                     svn_sort__item_t);
1030       const char *token = item->value;
1031       unlock_info_t info;
1032 
1033       svn_pool_clear(iterpool);
1034 
1035       info.path = item->key;
1036       info.fs_err = SVN_NO_ERROR;
1037       info.done = FALSE;
1038 
1039       if (!ub->skip_check)
1040         SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1041                              iterpool));
1042 
1043       /* If no error occurred while pre-checking, schedule the index updates for
1044          this path. */
1045       if (!info.fs_err)
1046         schedule_index_update(indices_updates, info.path, iterpool);
1047 
1048       APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info;
1049     }
1050 
1051   rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
1052 
1053   /* Unlike the lock_body(), we need to delete locks *before* we start to
1054      update indices. */
1055 
1056   for (i = 0; i < ub->infos->nelts; ++i)
1057     {
1058       struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
1059                                                   struct unlock_info_t);
1060 
1061       svn_pool_clear(iterpool);
1062 
1063       if (! info->fs_err)
1064         {
1065           SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1066           info->done = TRUE;
1067         }
1068     }
1069 
1070   for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
1071     {
1072       const char *path = apr_hash_this_key(hi);
1073       apr_array_header_t *children = apr_hash_this_val(hi);
1074 
1075       svn_pool_clear(iterpool);
1076       SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
1077                                  iterpool));
1078     }
1079 
1080   svn_pool_destroy(iterpool);
1081   return SVN_NO_ERROR;
1082 }
1083 
1084 /* Unlock the lock described by LOCK->path and LOCK->token in FS.
1085 
1086    This assumes that the write lock is held.
1087  */
1088 static svn_error_t *
unlock_single(svn_fs_t * fs,svn_lock_t * lock,apr_pool_t * scratch_pool)1089 unlock_single(svn_fs_t *fs,
1090               svn_lock_t *lock,
1091               apr_pool_t *scratch_pool)
1092 {
1093   unlock_baton_t ub;
1094   svn_sort__item_t item;
1095   apr_array_header_t *targets = apr_array_make(scratch_pool, 1,
1096                                                sizeof(svn_sort__item_t));
1097   item.key = lock->path;
1098   item.klen = strlen(item.key);
1099   item.value = (char*)lock->token;
1100   APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1101 
1102   ub.fs = fs;
1103   ub.targets = targets;
1104   ub.infos = apr_array_make(scratch_pool, targets->nelts,
1105                             sizeof(struct unlock_info_t));
1106   ub.skip_check = TRUE;
1107   ub.result_pool = scratch_pool;
1108 
1109   /* No ub.infos[].fs_err error because skip_check is TRUE. */
1110   SVN_ERR(unlock_body(&ub, scratch_pool));
1111 
1112   return SVN_NO_ERROR;
1113 }
1114 
1115 
1116 /*** Public API implementations ***/
1117 
1118 svn_error_t *
svn_fs_x__lock(svn_fs_t * fs,apr_hash_t * targets,const char * comment,svn_boolean_t is_dav_comment,apr_time_t expiration_date,svn_boolean_t steal_lock,svn_fs_lock_callback_t lock_callback,void * lock_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1119 svn_fs_x__lock(svn_fs_t *fs,
1120                apr_hash_t *targets,
1121                const char *comment,
1122                svn_boolean_t is_dav_comment,
1123                apr_time_t expiration_date,
1124                svn_boolean_t steal_lock,
1125                svn_fs_lock_callback_t lock_callback,
1126                void *lock_baton,
1127                apr_pool_t *result_pool,
1128                apr_pool_t *scratch_pool)
1129 {
1130   lock_baton_t lb;
1131   apr_array_header_t *sorted_targets;
1132   apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1133   apr_hash_index_t *hi;
1134   apr_pool_t *iterpool;
1135   svn_error_t *err, *cb_err = SVN_NO_ERROR;
1136   int i;
1137 
1138   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1139 
1140   /* We need to have a username attached to the fs. */
1141   if (!fs->access_ctx || !fs->access_ctx->username)
1142     return SVN_FS__ERR_NO_USER(fs);
1143 
1144   /* The FS locking API allows both canonical and non-canonical
1145      paths which means that the same canonical path could be
1146      represented more than once in the TARGETS hash.  We just keep
1147      one, choosing one with a token if possible. */
1148   for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1149     {
1150       const char *path = apr_hash_this_key(hi);
1151       const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1152       const svn_fs_lock_target_t *other;
1153 
1154       path = svn_fspath__canonicalize(path, result_pool);
1155       other = svn_hash_gets(canonical_targets, path);
1156 
1157       if (!other || (!other->token && target->token))
1158         svn_hash_sets(canonical_targets, path, target);
1159     }
1160 
1161   sorted_targets = svn_sort__hash(canonical_targets,
1162                                   svn_sort_compare_items_as_paths,
1163                                   scratch_pool);
1164 
1165   lb.fs = fs;
1166   lb.targets = sorted_targets;
1167   lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
1168                             sizeof(struct lock_info_t));
1169   lb.comment = comment;
1170   lb.is_dav_comment = is_dav_comment;
1171   lb.expiration_date = expiration_date;
1172   lb.steal_lock = steal_lock;
1173   lb.result_pool = result_pool;
1174 
1175   iterpool = svn_pool_create(scratch_pool);
1176   err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool);
1177   for (i = 0; i < lb.infos->nelts; ++i)
1178     {
1179       struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1180                                                 struct lock_info_t);
1181       svn_pool_clear(iterpool);
1182       if (!cb_err && lock_callback)
1183         {
1184           if (!info->lock && !info->fs_err)
1185             info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1186                                              0, _("Failed to lock '%s'"),
1187                                              info->path);
1188 
1189           cb_err = lock_callback(lock_baton, info->path, info->lock,
1190                                  info->fs_err, iterpool);
1191         }
1192       svn_error_clear(info->fs_err);
1193     }
1194   svn_pool_destroy(iterpool);
1195 
1196   if (err && cb_err)
1197     svn_error_compose(err, cb_err);
1198   else if (!err)
1199     err = cb_err;
1200 
1201   return svn_error_trace(err);
1202 }
1203 
1204 
1205 svn_error_t *
svn_fs_x__generate_lock_token(const char ** token,svn_fs_t * fs,apr_pool_t * pool)1206 svn_fs_x__generate_lock_token(const char **token,
1207                               svn_fs_t *fs,
1208                               apr_pool_t *pool)
1209 {
1210   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1211 
1212   /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
1213      want to use the fs UUID + some incremented number?  For now, we
1214      generate a URI that matches the DAV RFC.  We could change this to
1215      some other URI scheme someday, if we wish. */
1216   *token = apr_pstrcat(pool, "opaquelocktoken:",
1217                        svn_uuid_generate(pool), SVN_VA_NULL);
1218   return SVN_NO_ERROR;
1219 }
1220 
1221 svn_error_t *
svn_fs_x__unlock(svn_fs_t * fs,apr_hash_t * targets,svn_boolean_t break_lock,svn_fs_lock_callback_t lock_callback,void * lock_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1222 svn_fs_x__unlock(svn_fs_t *fs,
1223                  apr_hash_t *targets,
1224                  svn_boolean_t break_lock,
1225                  svn_fs_lock_callback_t lock_callback,
1226                  void *lock_baton,
1227                  apr_pool_t *result_pool,
1228                  apr_pool_t *scratch_pool)
1229 {
1230   unlock_baton_t ub;
1231   apr_array_header_t *sorted_targets;
1232   apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1233   apr_hash_index_t *hi;
1234   apr_pool_t *iterpool;
1235   svn_error_t *err, *cb_err = SVN_NO_ERROR;
1236   int i;
1237 
1238   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1239 
1240   /* We need to have a username attached to the fs. */
1241   if (!fs->access_ctx || !fs->access_ctx->username)
1242     return SVN_FS__ERR_NO_USER(fs);
1243 
1244   for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1245     {
1246       const char *path = apr_hash_this_key(hi);
1247       const char *token = apr_hash_this_val(hi);
1248       const char *other;
1249 
1250       path = svn_fspath__canonicalize(path, result_pool);
1251       other = svn_hash_gets(canonical_targets, path);
1252 
1253       if (!other)
1254         svn_hash_sets(canonical_targets, path, token);
1255     }
1256 
1257   sorted_targets = svn_sort__hash(canonical_targets,
1258                                   svn_sort_compare_items_as_paths,
1259                                   scratch_pool);
1260 
1261   ub.fs = fs;
1262   ub.targets = sorted_targets;
1263   ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
1264                             sizeof(struct unlock_info_t));
1265   ub.skip_check = FALSE;
1266   ub.break_lock = break_lock;
1267   ub.result_pool = result_pool;
1268 
1269   iterpool = svn_pool_create(scratch_pool);
1270   err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool);
1271   for (i = 0; i < ub.infos->nelts; ++i)
1272     {
1273       unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t);
1274       svn_pool_clear(iterpool);
1275       if (!cb_err && lock_callback)
1276         {
1277           if (!info->done && !info->fs_err)
1278             info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1279                                              0, _("Failed to unlock '%s'"),
1280                                              info->path);
1281           cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1282                                  iterpool);
1283         }
1284       svn_error_clear(info->fs_err);
1285     }
1286   svn_pool_destroy(iterpool);
1287 
1288   if (err && cb_err)
1289     svn_error_compose(err, cb_err);
1290   else if (!err)
1291     err = cb_err;
1292 
1293   return svn_error_trace(err);
1294 }
1295 
1296 
1297 svn_error_t *
svn_fs_x__get_lock(svn_lock_t ** lock_p,svn_fs_t * fs,const char * path,apr_pool_t * pool)1298 svn_fs_x__get_lock(svn_lock_t **lock_p,
1299                    svn_fs_t *fs,
1300                    const char *path,
1301                    apr_pool_t *pool)
1302 {
1303   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1304   path = svn_fs__canonicalize_abspath(path, pool);
1305   return get_lock_helper(fs, lock_p, path, FALSE, pool);
1306 }
1307 
1308 
1309 /* Baton for get_locks_filter_func(). */
1310 typedef struct get_locks_filter_baton_t
1311 {
1312   const char *path;
1313   svn_depth_t requested_depth;
1314   svn_fs_get_locks_callback_t get_locks_func;
1315   void *get_locks_baton;
1316 
1317 } get_locks_filter_baton_t;
1318 
1319 
1320 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks()
1321    which filters out locks on paths that aren't within
1322    BATON->requested_depth of BATON->path before called
1323    BATON->get_locks_func() with BATON->get_locks_baton.
1324 
1325    NOTE: See issue #3660 for details about how the FSX lock
1326    management code is inconsistent.  Until that inconsistency is
1327    resolved, we take this filtering approach rather than honoring
1328    depth requests closer to the crawling code.  In other words, once
1329    we decide how to resolve issue #3660, there might be a more
1330    performant way to honor the depth passed to svn_fs_x__get_locks().  */
1331 static svn_error_t *
get_locks_filter_func(void * baton,svn_lock_t * lock,apr_pool_t * pool)1332 get_locks_filter_func(void *baton,
1333                       svn_lock_t *lock,
1334                       apr_pool_t *pool)
1335 {
1336   get_locks_filter_baton_t *b = baton;
1337 
1338   /* Filter out unwanted paths.  Since Subversion only allows
1339      locks on files, we can treat depth=immediates the same as
1340      depth=files for filtering purposes.  Meaning, we'll keep
1341      this lock if:
1342 
1343      a) its path is the very path we queried, or
1344      b) we've asked for a fully recursive answer, or
1345      c) we've asked for depth=files or depth=immediates, and this
1346         lock is on an immediate child of our query path.
1347   */
1348   if ((strcmp(b->path, lock->path) == 0)
1349       || (b->requested_depth == svn_depth_infinity))
1350     {
1351       SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1352     }
1353   else if ((b->requested_depth == svn_depth_files) ||
1354            (b->requested_depth == svn_depth_immediates))
1355     {
1356       const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1357       if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1358         SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1359     }
1360 
1361   return SVN_NO_ERROR;
1362 }
1363 
1364 svn_error_t *
svn_fs_x__get_locks(svn_fs_t * fs,const char * path,svn_depth_t depth,svn_fs_get_locks_callback_t get_locks_func,void * get_locks_baton,apr_pool_t * scratch_pool)1365 svn_fs_x__get_locks(svn_fs_t *fs,
1366                     const char *path,
1367                     svn_depth_t depth,
1368                     svn_fs_get_locks_callback_t get_locks_func,
1369                     void *get_locks_baton,
1370                     apr_pool_t *scratch_pool)
1371 {
1372   const char *digest_path;
1373   get_locks_filter_baton_t glfb;
1374 
1375   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1376   path = svn_fs__canonicalize_abspath(path, scratch_pool);
1377 
1378   glfb.path = path;
1379   glfb.requested_depth = depth;
1380   glfb.get_locks_func = get_locks_func;
1381   glfb.get_locks_baton = get_locks_baton;
1382 
1383   /* Get the top digest path in our tree of interest, and then walk it. */
1384   SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool));
1385   SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1386                      FALSE, scratch_pool));
1387   return SVN_NO_ERROR;
1388 }
1389