1 /* lock-test.c --- tests for the filesystem locking functions
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 <string.h>
24 #include <apr_pools.h>
25 #include <apr_time.h>
26 
27 #include "../svn_test.h"
28 
29 #include "svn_error.h"
30 #include "svn_fs.h"
31 #include "svn_hash.h"
32 
33 #include "../svn_test_fs.h"
34 
35 
36 /*-----------------------------------------------------------------*/
37 
38 /** Helper functions **/
39 
40 /* Implementations of the svn_fs_get_locks_callback_t interface and
41    baton, for verifying expected output from svn_fs_get_locks(). */
42 
43 struct get_locks_baton_t
44 {
45   apr_hash_t *locks;
46 };
47 
48 static svn_error_t *
get_locks_callback(void * baton,svn_lock_t * lock,apr_pool_t * pool)49 get_locks_callback(void *baton,
50                    svn_lock_t *lock,
51                    apr_pool_t *pool)
52 {
53   struct get_locks_baton_t *b = baton;
54   apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
55   svn_string_t *lock_path = svn_string_create(lock->path, hash_pool);
56 
57   if (!apr_hash_get(b->locks, lock_path->data, lock_path->len))
58     {
59       apr_hash_set(b->locks, lock_path->data, lock_path->len,
60                    svn_lock_dup(lock, hash_pool));
61       return SVN_NO_ERROR;
62     }
63   else
64     {
65       return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
66                                "Lock for path '%s' is being reported twice.",
67                                lock->path);
68     }
69 }
70 
71 /* A factory function. */
72 
73 static struct get_locks_baton_t *
make_get_locks_baton(apr_pool_t * pool)74 make_get_locks_baton(apr_pool_t *pool)
75 {
76   struct get_locks_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
77   baton->locks = apr_hash_make(pool);
78   return baton;
79 }
80 
81 
82 /* And verification function(s). */
83 
84 static svn_error_t *
verify_matching_lock_paths(struct get_locks_baton_t * baton,const char * expected_paths[],apr_size_t num_expected_paths,apr_pool_t * pool)85 verify_matching_lock_paths(struct get_locks_baton_t *baton,
86                            const char *expected_paths[],
87                            apr_size_t num_expected_paths,
88                            apr_pool_t *pool)
89 {
90   apr_size_t i;
91   if (num_expected_paths != apr_hash_count(baton->locks))
92     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
93                             "Unexpected number of locks.");
94   for (i = 0; i < num_expected_paths; i++)
95     {
96       const char *path = expected_paths[i];
97       if (! apr_hash_get(baton->locks, path, APR_HASH_KEY_STRING))
98         return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
99                                  "Missing lock for path '%s'", path);
100     }
101   return SVN_NO_ERROR;
102 }
103 
104 
105 /* Create a filesystem in a directory called NAME, and populate it with
106  * the standard Greek tree.  Set *FS_P to the new filesystem object and
107  * *NEWREV_P to the head revision number.  Unwanted outputs may be NULL. */
108 static svn_error_t *
create_greek_fs(svn_fs_t ** fs_p,svn_revnum_t * newrev_p,const char * name,const svn_test_opts_t * opts,apr_pool_t * pool)109 create_greek_fs(svn_fs_t **fs_p,
110                 svn_revnum_t *newrev_p,
111                 const char *name,
112                 const svn_test_opts_t *opts,
113                 apr_pool_t *pool)
114 {
115   svn_fs_t *fs;
116   svn_fs_txn_t *txn;
117   svn_fs_root_t *txn_root;
118   const char *conflict;
119   svn_revnum_t newrev;
120 
121   /* Prepare a filesystem and a new txn. */
122   SVN_ERR(svn_test__create_fs(&fs, name, opts, pool));
123   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
124   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
125 
126   /* Create the greek tree and commit it. */
127   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
128   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
129   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(newrev));
130 
131   if (fs_p)
132     *fs_p = fs;
133   if (newrev_p)
134     *newrev_p = newrev;
135   return SVN_NO_ERROR;
136 }
137 
138 
139 /*-----------------------------------------------------------------*/
140 
141 /** The actual lock-tests called by `make check` **/
142 
143 
144 
145 /* Test that we can create a lock--nothing more.  */
146 static svn_error_t *
lock_only(const svn_test_opts_t * opts,apr_pool_t * pool)147 lock_only(const svn_test_opts_t *opts,
148           apr_pool_t *pool)
149 {
150   svn_fs_t *fs;
151   svn_fs_access_t *access;
152   svn_lock_t *mylock;
153 
154   SVN_ERR(create_greek_fs(&fs, NULL, "test-repo-lock-only",
155                           opts, pool));
156 
157   /* We are now 'bubba'. */
158   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
159   SVN_ERR(svn_fs_set_access(fs, access));
160 
161   /* Lock /A/D/G/rho. */
162   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
163                       SVN_INVALID_REVNUM, FALSE, pool));
164 
165   return SVN_NO_ERROR;
166 }
167 
168 
169 
170 
171 
172 /* Test that we can create, fetch, and destroy a lock.  It exercises
173    each of the five public fs locking functions.  */
174 static svn_error_t *
lookup_lock_by_path(const svn_test_opts_t * opts,apr_pool_t * pool)175 lookup_lock_by_path(const svn_test_opts_t *opts,
176                     apr_pool_t *pool)
177 {
178   svn_fs_t *fs;
179   svn_fs_access_t *access;
180   svn_lock_t *mylock, *somelock;
181 
182   SVN_ERR(create_greek_fs(&fs, NULL, "test-repo-lookup-lock-by-path",
183                           opts, pool));
184 
185   /* We are now 'bubba'. */
186   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
187   SVN_ERR(svn_fs_set_access(fs, access));
188 
189   /* Lock /A/D/G/rho. */
190   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
191                       SVN_INVALID_REVNUM, FALSE, pool));
192 
193   /* Can we look up the lock by path? */
194   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
195   if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0))
196     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
197                             "Couldn't look up a lock by pathname.");
198 
199   return SVN_NO_ERROR;
200 }
201 
202 /* Test that we can create a lock outside of the fs and attach it to a
203    path.  */
204 static svn_error_t *
attach_lock(const svn_test_opts_t * opts,apr_pool_t * pool)205 attach_lock(const svn_test_opts_t *opts,
206             apr_pool_t *pool)
207 {
208   svn_fs_t *fs;
209   svn_fs_access_t *access;
210   svn_lock_t *somelock;
211   svn_lock_t *mylock;
212   const char *token;
213 
214   SVN_ERR(create_greek_fs(&fs, NULL, "test-repo-attach-lock",
215                           opts, pool));
216 
217   /* We are now 'bubba'. */
218   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
219   SVN_ERR(svn_fs_set_access(fs, access));
220 
221   SVN_ERR(svn_fs_generate_lock_token(&token, fs, pool));
222   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", token,
223                       "This is a comment.  Yay comment!", 0,
224                       apr_time_now() + apr_time_from_sec(3),
225                       SVN_INVALID_REVNUM, FALSE, pool));
226 
227   /* Can we look up the lock by path? */
228   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
229   if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0))
230     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
231                             "Couldn't look up a lock by pathname.");
232 
233   /* Unlock /A/D/G/rho, and verify that it's gone. */
234   SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 0, pool));
235   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
236   if (somelock)
237     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
238                             "Removed a lock, but it's still there.");
239 
240   return SVN_NO_ERROR;
241 }
242 
243 
244 /* Test that we can get all locks under a directory. */
245 static svn_error_t *
get_locks(const svn_test_opts_t * opts,apr_pool_t * pool)246 get_locks(const svn_test_opts_t *opts,
247           apr_pool_t *pool)
248 {
249   svn_fs_t *fs;
250   svn_fs_access_t *access;
251   svn_lock_t *mylock;
252   struct get_locks_baton_t *get_locks_baton;
253   apr_size_t i, num_expected_paths;
254 
255   SVN_ERR(create_greek_fs(&fs, NULL, "test-repo-get-locks",
256                           opts, pool));
257 
258   /* We are now 'bubba'. */
259   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
260   SVN_ERR(svn_fs_set_access(fs, access));
261 
262   /* Lock our paths; verify from "/". */
263   {
264     static const char *expected_paths[] = {
265       "/A/D/G/pi",
266       "/A/D/G/rho",
267       "/A/D/G/tau",
268       "/A/D/H/psi",
269       "/A/D/H/chi",
270       "/A/D/H/omega",
271       "/A/B/E/alpha",
272       "/A/B/E/beta",
273     };
274     num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
275     for (i = 0; i < num_expected_paths; i++)
276       {
277         SVN_ERR(svn_fs_lock(&mylock, fs, expected_paths[i], NULL, "", 0, 0,
278                             SVN_INVALID_REVNUM, FALSE, pool));
279       }
280     get_locks_baton = make_get_locks_baton(pool);
281     SVN_ERR(svn_fs_get_locks(fs, "", get_locks_callback,
282                              get_locks_baton, pool));
283     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
284                                        num_expected_paths, pool));
285   }
286 
287   /* Verify from "/A/B". */
288   {
289     static const char *expected_paths[] = {
290       "/A/B/E/alpha",
291       "/A/B/E/beta",
292     };
293     num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
294     get_locks_baton = make_get_locks_baton(pool);
295     SVN_ERR(svn_fs_get_locks(fs, "A/B", get_locks_callback,
296                              get_locks_baton, pool));
297     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
298                                        num_expected_paths, pool));
299   }
300 
301   /* Verify from "/A/D". */
302   {
303     static const char *expected_paths[] = {
304       "/A/D/G/pi",
305       "/A/D/G/rho",
306       "/A/D/G/tau",
307       "/A/D/H/psi",
308       "/A/D/H/chi",
309       "/A/D/H/omega",
310     };
311     num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
312     get_locks_baton = make_get_locks_baton(pool);
313     SVN_ERR(svn_fs_get_locks(fs, "A/D", get_locks_callback,
314                              get_locks_baton, pool));
315     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
316                                        num_expected_paths, pool));
317   }
318 
319   /* Verify from "/A/D/G". */
320   {
321     static const char *expected_paths[] = {
322       "/A/D/G/pi",
323       "/A/D/G/rho",
324       "/A/D/G/tau",
325     };
326     num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
327     get_locks_baton = make_get_locks_baton(pool);
328     SVN_ERR(svn_fs_get_locks(fs, "A/D/G", get_locks_callback,
329                              get_locks_baton, pool));
330     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
331                                        num_expected_paths, pool));
332   }
333 
334   /* Verify from "/A/D/H/omega". */
335   {
336     static const char *expected_paths[] = {
337       "/A/D/H/omega",
338     };
339     num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
340     get_locks_baton = make_get_locks_baton(pool);
341     SVN_ERR(svn_fs_get_locks(fs, "A/D/H/omega", get_locks_callback,
342                              get_locks_baton, pool));
343     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
344                                        num_expected_paths, pool));
345   }
346 
347   /* Verify from "/iota" (which wasn't locked... tricky...). */
348   {
349     static const char *expected_paths[] = { 0 };
350     num_expected_paths = 0;
351     get_locks_baton = make_get_locks_baton(pool);
352     SVN_ERR(svn_fs_get_locks(fs, "iota", get_locks_callback,
353                              get_locks_baton, pool));
354     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
355                                        num_expected_paths, pool));
356   }
357 
358   /* A path that is longer and alphabetically earlier than some locked
359      paths, this exercises the r1205848 BDB lock code. */
360   {
361     static const char *expected_paths[] = { 0 };
362     num_expected_paths = 0;
363     get_locks_baton = make_get_locks_baton(pool);
364     SVN_ERR(svn_fs_get_locks(fs, "A/D/H/ABCDEFGHIJKLMNOPQR", get_locks_callback,
365                              get_locks_baton, pool));
366     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
367                                        num_expected_paths, pool));
368   }
369 
370   return SVN_NO_ERROR;
371 }
372 
373 
374 /* Test that we can create, fetch, and destroy a lock.  It exercises
375    each of the five public fs locking functions.  */
376 static svn_error_t *
basic_lock(const svn_test_opts_t * opts,apr_pool_t * pool)377 basic_lock(const svn_test_opts_t *opts,
378            apr_pool_t *pool)
379 {
380   svn_fs_t *fs;
381   svn_fs_access_t *access;
382   svn_lock_t *mylock, *somelock;
383 
384   SVN_ERR(create_greek_fs(&fs, NULL, "test-repo-basic-lock",
385                           opts, pool));
386 
387   /* We are now 'bubba'. */
388   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
389   SVN_ERR(svn_fs_set_access(fs, access));
390 
391   /* Lock /A/D/G/rho. */
392   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
393                       SVN_INVALID_REVNUM, FALSE, pool));
394 
395   /* Can we look up the lock by path? */
396   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
397   if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0))
398     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
399                             "Couldn't look up a lock by pathname.");
400 
401   /* Unlock /A/D/G/rho, and verify that it's gone. */
402   SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 0, pool));
403   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
404   if (somelock)
405     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
406                             "Removed a lock, but it's still there.");
407 
408   return SVN_NO_ERROR;
409 }
410 
411 
412 
413 /* Test that locks are enforced -- specifically that both a username
414    and token are required to make use of the lock.  */
415 static svn_error_t *
lock_credentials(const svn_test_opts_t * opts,apr_pool_t * pool)416 lock_credentials(const svn_test_opts_t *opts,
417                  apr_pool_t *pool)
418 {
419   svn_fs_t *fs;
420   svn_fs_txn_t *txn;
421   svn_fs_root_t *txn_root;
422   const char *conflict;
423   svn_revnum_t newrev;
424   svn_fs_access_t *access;
425   svn_lock_t *mylock;
426   svn_error_t *err;
427 
428   SVN_ERR(create_greek_fs(&fs, &newrev, "test-repo-lock-credentials",
429                           opts, pool));
430 
431   /* We are now 'bubba'. */
432   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
433   SVN_ERR(svn_fs_set_access(fs, access));
434 
435   /* Lock /A/D/G/rho. */
436   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
437                       SVN_INVALID_REVNUM, FALSE, pool));
438 
439   /* Push the proper lock-token into the fs access context. */
440   SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token));
441 
442   /* Make a new transaction and change rho. */
443   SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
444   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
445   SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho",
446                                       "new contents", pool));
447 
448   /* We are no longer 'bubba'.  We're nobody. */
449   SVN_ERR(svn_fs_set_access(fs, NULL));
450 
451   /* Try to commit the file change.  Should fail, because we're nobody. */
452   err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
453   SVN_TEST_ASSERT(! SVN_IS_VALID_REVNUM(newrev));
454   if (! err)
455     return svn_error_create
456       (SVN_ERR_TEST_FAILED, NULL,
457        "Uhoh, able to commit locked file without any fs username.");
458   svn_error_clear(err);
459 
460   /* We are now 'hortense'. */
461   SVN_ERR(svn_fs_create_access(&access, "hortense", pool));
462   SVN_ERR(svn_fs_set_access(fs, access));
463 
464   /* Try to commit the file change.  Should fail, because we're 'hortense'. */
465   err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
466   SVN_TEST_ASSERT(! SVN_IS_VALID_REVNUM(newrev));
467   if (! err)
468     return svn_error_create
469       (SVN_ERR_TEST_FAILED, NULL,
470        "Uhoh, able to commit locked file as non-owner.");
471   svn_error_clear(err);
472 
473   /* Be 'bubba' again. */
474   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
475   SVN_ERR(svn_fs_set_access(fs, access));
476 
477   /* Try to commit the file change.  Should fail, because there's no token. */
478   err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
479   SVN_TEST_ASSERT(! SVN_IS_VALID_REVNUM(newrev));
480   if (! err)
481     return svn_error_create
482       (SVN_ERR_TEST_FAILED, NULL,
483        "Uhoh, able to commit locked file with no lock token.");
484   svn_error_clear(err);
485 
486   /* Push the proper lock-token into the fs access context. */
487   SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token));
488 
489   /* Commit should now succeed. */
490   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
491   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(newrev));
492 
493   return SVN_NO_ERROR;
494 }
495 
496 
497 
498 /* Test that locks are enforced at commit time.  Somebody might lock
499    something behind your back, right before you run
500    svn_fs_commit_txn().  Also, this test verifies that recursive
501    lock-checks on directories is working properly. */
502 static svn_error_t *
final_lock_check(const svn_test_opts_t * opts,apr_pool_t * pool)503 final_lock_check(const svn_test_opts_t *opts,
504                  apr_pool_t *pool)
505 {
506   svn_fs_t *fs;
507   svn_fs_txn_t *txn;
508   svn_fs_root_t *txn_root;
509   const char *conflict;
510   svn_revnum_t newrev;
511   svn_fs_access_t *access;
512   svn_lock_t *mylock;
513   svn_error_t *err;
514 
515   SVN_ERR(create_greek_fs(&fs, &newrev, "test-repo-final-lock-check",
516                           opts, pool));
517 
518   /* Make a new transaction and delete "/A" */
519   SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
520   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
521   SVN_ERR(svn_fs_delete(txn_root, "/A", pool));
522 
523   /* Become 'bubba' and lock "/A/D/G/rho". */
524   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
525   SVN_ERR(svn_fs_set_access(fs, access));
526   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
527                       SVN_INVALID_REVNUM, FALSE, pool));
528 
529   /* We are no longer 'bubba'.  We're nobody. */
530   SVN_ERR(svn_fs_set_access(fs, NULL));
531 
532   /* Try to commit the transaction.  Should fail, because a child of
533      the deleted directory is locked by someone else. */
534   err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
535   SVN_TEST_ASSERT(! SVN_IS_VALID_REVNUM(newrev));
536   if (! err)
537     return svn_error_create
538       (SVN_ERR_TEST_FAILED, NULL,
539        "Uhoh, able to commit dir deletion when a child is locked.");
540   svn_error_clear(err);
541 
542   /* Supply correct username and token;  commit should work. */
543   SVN_ERR(svn_fs_set_access(fs, access));
544   SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token));
545   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
546   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(newrev));
547 
548   return SVN_NO_ERROR;
549 }
550 
551 
552 
553 /* If a directory's child is locked by someone else, we should still
554    be able to commit a propchange on the directory. */
555 static svn_error_t *
lock_dir_propchange(const svn_test_opts_t * opts,apr_pool_t * pool)556 lock_dir_propchange(const svn_test_opts_t *opts,
557                     apr_pool_t *pool)
558 {
559   svn_fs_t *fs;
560   svn_fs_txn_t *txn;
561   svn_fs_root_t *txn_root;
562   const char *conflict;
563   svn_revnum_t newrev;
564   svn_fs_access_t *access;
565   svn_lock_t *mylock;
566 
567   SVN_ERR(create_greek_fs(&fs, &newrev, "test-repo-lock-dir-propchange",
568                           opts, pool));
569 
570   /* Become 'bubba' and lock "/A/D/G/rho". */
571   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
572   SVN_ERR(svn_fs_set_access(fs, access));
573   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
574                       SVN_INVALID_REVNUM, FALSE, pool));
575 
576   /* We are no longer 'bubba'.  We're nobody. */
577   SVN_ERR(svn_fs_set_access(fs, NULL));
578 
579   /* Make a new transaction and make a propchange on "/A" */
580   SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
581   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
582   SVN_ERR(svn_fs_change_node_prop(txn_root, "/A",
583                                   "foo", svn_string_create("bar", pool),
584                                   pool));
585 
586   /* Commit should succeed;  this means we're doing a non-recursive
587      lock-check on directory, rather than a recursive one.  */
588   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
589   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(newrev));
590 
591   return SVN_NO_ERROR;
592 }
593 
594 /* Test that locks auto-expire correctly. */
595 static svn_error_t *
lock_expiration(const svn_test_opts_t * opts,apr_pool_t * pool)596 lock_expiration(const svn_test_opts_t *opts,
597                 apr_pool_t *pool)
598 {
599   svn_fs_t *fs;
600   svn_fs_txn_t *txn;
601   svn_fs_root_t *txn_root;
602   const char *conflict;
603   svn_revnum_t newrev;
604   svn_fs_access_t *access;
605   svn_lock_t *mylock;
606   svn_error_t *err;
607   struct get_locks_baton_t *get_locks_baton;
608 
609   SVN_ERR(create_greek_fs(&fs, &newrev, "test-repo-lock-expiration",
610                           opts, pool));
611 
612   /* Make a new transaction and change rho. */
613   SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
614   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
615   SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho",
616                                       "new contents", pool));
617 
618   /* We are now 'bubba'. */
619   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
620   SVN_ERR(svn_fs_set_access(fs, access));
621 
622   /* Lock /A/D/G/rho, with an expiration 2 seconds from now. */
623   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0,
624                       apr_time_now() + apr_time_from_sec(2),
625                       SVN_INVALID_REVNUM, FALSE, pool));
626 
627   /* Become nobody. */
628   SVN_ERR(svn_fs_set_access(fs, NULL));
629 
630   /* Try to commit.  Should fail because we're 'nobody', and the lock
631      hasn't expired yet. */
632   err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
633   SVN_TEST_ASSERT(! SVN_IS_VALID_REVNUM(newrev));
634   if (! err)
635     return svn_error_create
636       (SVN_ERR_TEST_FAILED, NULL,
637        "Uhoh, able to commit a file that has a non-expired lock.");
638   svn_error_clear(err);
639 
640   /* Check that the lock is there, by getting it via the paths parent. */
641   {
642     static const char *expected_paths [] = {
643       "/A/D/G/rho"
644     };
645     apr_size_t num_expected_paths = (sizeof(expected_paths)
646                                      / sizeof(expected_paths[0]));
647     get_locks_baton = make_get_locks_baton(pool);
648     SVN_ERR(svn_fs_get_locks(fs, "/A/D/G", get_locks_callback,
649                              get_locks_baton, pool));
650     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
651                                        num_expected_paths, pool));
652   }
653 
654   /* Sleep 2 seconds, so the lock auto-expires.  Anonymous commit
655      should then succeed. */
656   apr_sleep(apr_time_from_sec(3));
657 
658   /* Verify that the lock auto-expired even in the recursive case. */
659   {
660     static const char *expected_paths [] = { 0 };
661     apr_size_t num_expected_paths = 0;
662     get_locks_baton = make_get_locks_baton(pool);
663     SVN_ERR(svn_fs_get_locks(fs, "/A/D/G", get_locks_callback,
664                              get_locks_baton, pool));
665     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
666                                        num_expected_paths, pool));
667   }
668 
669   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
670   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(newrev));
671 
672   return SVN_NO_ERROR;
673 }
674 
675 /* Test that a lock can be broken, stolen, or refreshed */
676 static svn_error_t *
lock_break_steal_refresh(const svn_test_opts_t * opts,apr_pool_t * pool)677 lock_break_steal_refresh(const svn_test_opts_t *opts,
678                          apr_pool_t *pool)
679 {
680   svn_fs_t *fs;
681   svn_fs_access_t *access;
682   svn_lock_t *mylock, *somelock;
683 
684   SVN_ERR(create_greek_fs(&fs, NULL, "test-repo-steal-refresh",
685                           opts, pool));
686 
687   /* Become 'bubba' and lock "/A/D/G/rho". */
688   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
689   SVN_ERR(svn_fs_set_access(fs, access));
690   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
691                       SVN_INVALID_REVNUM, FALSE, pool));
692 
693   /* Become 'hortense' and break bubba's lock, then verify it's gone. */
694   SVN_ERR(svn_fs_create_access(&access, "hortense", pool));
695   SVN_ERR(svn_fs_set_access(fs, access));
696   SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token,
697                         1 /* FORCE BREAK */, pool));
698   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
699   if (somelock)
700     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
701                             "Tried to break a lock, but it's still there.");
702 
703   /* As hortense, create a new lock, and verify that we own it. */
704   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
705                       SVN_INVALID_REVNUM, FALSE, pool));
706   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
707   if (strcmp(somelock->owner, mylock->owner) != 0)
708     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
709                             "Made a lock, but we don't seem to own it.");
710 
711   /* As bubba, steal hortense's lock, creating a new one that expires. */
712   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
713   SVN_ERR(svn_fs_set_access(fs, access));
714   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0,
715                       apr_time_now() + apr_time_from_sec(300), /* 5 min. */
716                       SVN_INVALID_REVNUM,
717                       TRUE /* FORCE STEAL */,
718                       pool));
719   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
720   if (strcmp(somelock->owner, mylock->owner) != 0)
721     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
722                             "Made a lock, but we don't seem to own it.");
723   if (! somelock->expiration_date)
724     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
725                             "Made expiring lock, but seems not to expire.");
726 
727   /* Refresh the lock, so that it never expires. */
728   SVN_ERR(svn_fs_lock(&somelock, fs, somelock->path, somelock->token,
729                       somelock->comment, 0, 0,
730                       SVN_INVALID_REVNUM,
731                       TRUE /* FORCE STEAL */,
732                       pool));
733   SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
734   if (somelock->expiration_date)
735     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
736                             "Made non-expirirng lock, but it expires.");
737 
738   return SVN_NO_ERROR;
739 }
740 
741 
742 /* Test that svn_fs_lock() and svn_fs_attach_lock() can do
743    out-of-dateness checks..  */
744 static svn_error_t *
lock_out_of_date(const svn_test_opts_t * opts,apr_pool_t * pool)745 lock_out_of_date(const svn_test_opts_t *opts,
746                  apr_pool_t *pool)
747 {
748   svn_fs_t *fs;
749   svn_fs_txn_t *txn;
750   svn_fs_root_t *txn_root;
751   const char *conflict;
752   svn_revnum_t newrev;
753   svn_fs_access_t *access;
754   svn_lock_t *mylock;
755   svn_error_t *err;
756 
757   SVN_ERR(create_greek_fs(&fs, &newrev, "test-repo-lock-out-of-date",
758                           opts, pool));
759 
760   /* Commit a small change to /A/D/G/rho, creating revision 2. */
761   SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
762   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
763   SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho",
764                                       "new contents", pool));
765   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
766   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(newrev));
767 
768   /* We are now 'bubba'. */
769   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
770   SVN_ERR(svn_fs_set_access(fs, access));
771 
772   /* Try to lock /A/D/G/rho, but claim that we still have r1 of the file. */
773   err = svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, 1, FALSE, pool);
774   if (! err)
775     return svn_error_create
776       (SVN_ERR_TEST_FAILED, NULL,
777        "Uhoh, able to lock an out-of-date file.");
778   svn_error_clear(err);
779 
780   /* Attempt lock again, this time claiming to have r2. */
781   SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0,
782                       0, 2, FALSE, pool));
783 
784   /* 'Refresh' the lock, claiming to have r1... should fail. */
785   err = svn_fs_lock(&mylock, fs, mylock->path,
786                     mylock->token, mylock->comment, 0,
787                     apr_time_now() + apr_time_from_sec(50),
788                     1,
789                     TRUE /* FORCE STEAL */,
790                     pool);
791   if (! err)
792     return svn_error_create
793       (SVN_ERR_TEST_FAILED, NULL,
794        "Uhoh, able to refresh a lock on an out-of-date file.");
795   svn_error_clear(err);
796 
797   return SVN_NO_ERROR;
798 }
799 
800 struct lock_result_t {
801   const svn_lock_t *lock;
802   svn_error_t *fs_err;
803 };
804 
805 static svn_error_t *
expect_lock(const char * path,apr_hash_t * results,svn_fs_t * fs,apr_pool_t * scratch_pool)806 expect_lock(const char *path,
807             apr_hash_t *results,
808             svn_fs_t *fs,
809             apr_pool_t *scratch_pool)
810 {
811   svn_lock_t *lock;
812   struct lock_result_t *result = svn_hash_gets(results, path);
813 
814   SVN_TEST_ASSERT(result && result->lock && !result->fs_err);
815   SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
816   SVN_TEST_ASSERT(lock);
817   return SVN_NO_ERROR;
818 }
819 
820 static svn_error_t *
expect_error(const char * path,apr_hash_t * results,svn_fs_t * fs,apr_pool_t * scratch_pool)821 expect_error(const char *path,
822              apr_hash_t *results,
823              svn_fs_t *fs,
824              apr_pool_t *scratch_pool)
825 {
826   svn_lock_t *lock;
827   struct lock_result_t *result = svn_hash_gets(results, path);
828 
829   SVN_TEST_ASSERT(result && !result->lock && result->fs_err);
830   svn_error_clear(result->fs_err);
831   SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
832   SVN_TEST_ASSERT(!lock);
833   return SVN_NO_ERROR;
834 }
835 
836 static svn_error_t *
expect_unlock(const char * path,apr_hash_t * results,svn_fs_t * fs,apr_pool_t * scratch_pool)837 expect_unlock(const char *path,
838               apr_hash_t *results,
839               svn_fs_t *fs,
840               apr_pool_t *scratch_pool)
841 {
842   svn_lock_t *lock;
843   struct lock_result_t *result = svn_hash_gets(results, path);
844 
845   SVN_TEST_ASSERT(result && !result->fs_err);
846   SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
847   SVN_TEST_ASSERT(!lock);
848   return SVN_NO_ERROR;
849 }
850 
851 static svn_error_t *
expect_unlock_error(const char * path,apr_hash_t * results,svn_fs_t * fs,apr_pool_t * scratch_pool)852 expect_unlock_error(const char *path,
853                     apr_hash_t *results,
854                     svn_fs_t *fs,
855                     apr_pool_t *scratch_pool)
856 {
857   svn_lock_t *lock;
858   struct lock_result_t *result = svn_hash_gets(results, path);
859 
860   SVN_TEST_ASSERT(result && result->fs_err);
861   svn_error_clear(result->fs_err);
862   SVN_ERR(svn_fs_get_lock(&lock, fs, path, scratch_pool));
863   SVN_TEST_ASSERT(lock);
864   return SVN_NO_ERROR;
865 }
866 
867 struct lock_many_baton_t {
868   apr_hash_t *results;
869   apr_pool_t *pool;
870   int count;
871 };
872 
873 /* Implements svn_fs_lock_callback_t. */
874 static svn_error_t *
lock_many_cb(void * lock_baton,const char * path,const svn_lock_t * lock,svn_error_t * fs_err,apr_pool_t * pool)875 lock_many_cb(void *lock_baton,
876              const char *path,
877              const svn_lock_t *lock,
878              svn_error_t *fs_err,
879              apr_pool_t *pool)
880 {
881   struct lock_many_baton_t *b = lock_baton;
882   struct lock_result_t *result = apr_palloc(b->pool,
883                                             sizeof(struct lock_result_t));
884 
885   result->lock = lock;
886   result->fs_err = svn_error_dup(fs_err);
887   svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result);
888 
889   if (b->count)
890     if (!--(b->count))
891       return svn_error_create(SVN_ERR_FS_GENERAL, NULL, "lock_many_cb");
892 
893   return SVN_NO_ERROR;
894 }
895 
896 static svn_error_t *
lock_multiple_paths(const svn_test_opts_t * opts,apr_pool_t * pool)897 lock_multiple_paths(const svn_test_opts_t *opts,
898                     apr_pool_t *pool)
899 {
900   svn_fs_t *fs;
901   svn_fs_txn_t *txn;
902   svn_fs_root_t *root, *txn_root;
903   const char *conflict;
904   svn_revnum_t newrev;
905   svn_fs_access_t *access;
906   svn_fs_lock_target_t *target;
907   struct lock_many_baton_t baton;
908   apr_hash_t *lock_paths, *unlock_paths;
909   apr_hash_index_t *hi;
910 
911   SVN_ERR(create_greek_fs(&fs, &newrev, "test-lock-multiple-paths",
912                           opts, pool));
913   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
914   SVN_ERR(svn_fs_set_access(fs, access));
915   SVN_ERR(svn_fs_revision_root(&root, fs, newrev, pool));
916   SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
917   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
918   SVN_ERR(svn_fs_make_dir(txn_root, "/A/BB", pool));
919   SVN_ERR(svn_fs_make_dir(txn_root, "/A/BBB", pool));
920   SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/A/BB/mu", pool));
921   SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/A/BBB/mu", pool));
922   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
923 
924   baton.results = apr_hash_make(pool);
925   baton.pool = pool;
926   baton.count = 0;
927   lock_paths = apr_hash_make(pool);
928   unlock_paths = apr_hash_make(pool);
929   target = svn_fs_lock_target_create(NULL, newrev, pool);
930 
931   svn_hash_sets(lock_paths, "/A/B/E/alpha", target);
932   svn_hash_sets(lock_paths, "/A/B/E/beta", target);
933   svn_hash_sets(lock_paths, "/A/B/E/zulu", target);
934   svn_hash_sets(lock_paths, "/A/BB/mu", target);
935   svn_hash_sets(lock_paths, "/A/BBB/mu", target);
936   svn_hash_sets(lock_paths, "/A/D/G/pi", target);
937   svn_hash_sets(lock_paths, "/A/D/G/rho", target);
938   svn_hash_sets(lock_paths, "/A/mu", target);
939   svn_hash_sets(lock_paths, "/X/zulu", target);
940 
941   /* Lock some paths. */
942   apr_hash_clear(baton.results);
943   SVN_ERR(svn_fs_lock_many(fs, lock_paths, "comment", 0, 0, 0,
944                            lock_many_cb, &baton,
945                            pool, pool));
946 
947   SVN_ERR(expect_lock("/A/B/E/alpha", baton.results, fs, pool));
948   SVN_ERR(expect_lock("/A/B/E/beta", baton.results, fs, pool));
949   SVN_ERR(expect_error("/A/B/E/zulu", baton.results, fs, pool));
950   SVN_ERR(expect_lock("/A/BB/mu", baton.results, fs, pool));
951   SVN_ERR(expect_lock("/A/BBB/mu", baton.results, fs, pool));
952   SVN_ERR(expect_lock("/A/D/G/pi", baton.results, fs, pool));
953   SVN_ERR(expect_lock("/A/D/G/rho", baton.results, fs, pool));
954   SVN_ERR(expect_lock("/A/mu", baton.results, fs, pool));
955   SVN_ERR(expect_error("/X/zulu", baton.results, fs, pool));
956 
957   /* Unlock without force and wrong tokens. */
958   for (hi = apr_hash_first(pool, lock_paths); hi; hi = apr_hash_next(hi))
959     svn_hash_sets(unlock_paths, apr_hash_this_key(hi), "wrong-token");
960   apr_hash_clear(baton.results);
961   SVN_ERR(svn_fs_unlock_many(fs, unlock_paths, FALSE, lock_many_cb, &baton,
962                              pool, pool));
963 
964   SVN_ERR(expect_unlock_error("/A/B/E/alpha", baton.results, fs, pool));
965   SVN_ERR(expect_unlock_error("/A/B/E/beta", baton.results, fs, pool));
966   SVN_ERR(expect_error("/A/B/E/zulu", baton.results, fs, pool));
967   SVN_ERR(expect_unlock_error("/A/BB/mu", baton.results, fs, pool));
968   SVN_ERR(expect_unlock_error("/A/BBB/mu", baton.results, fs, pool));
969   SVN_ERR(expect_unlock_error("/A/D/G/pi", baton.results, fs, pool));
970   SVN_ERR(expect_unlock_error("/A/D/G/rho", baton.results, fs, pool));
971   SVN_ERR(expect_unlock_error("/A/mu", baton.results, fs, pool));
972   SVN_ERR(expect_error("/X/zulu", baton.results, fs, pool));
973 
974   /* Force unlock. */
975   for (hi = apr_hash_first(pool, lock_paths); hi; hi = apr_hash_next(hi))
976     svn_hash_sets(unlock_paths, apr_hash_this_key(hi), "");
977   apr_hash_clear(baton.results);
978   SVN_ERR(svn_fs_unlock_many(fs, unlock_paths, TRUE, lock_many_cb, &baton,
979                              pool, pool));
980 
981   SVN_ERR(expect_unlock("/A/B/E/alpha", baton.results, fs, pool));
982   SVN_ERR(expect_unlock("/A/B/E/beta", baton.results, fs, pool));
983   SVN_ERR(expect_error("/A/B/E/zulu", baton.results, fs, pool));
984   SVN_ERR(expect_unlock("/A/BB/mu", baton.results, fs, pool));
985   SVN_ERR(expect_unlock("/A/BBB/mu", baton.results, fs, pool));
986   SVN_ERR(expect_unlock("/A/D/G/pi", baton.results, fs, pool));
987   SVN_ERR(expect_unlock("/A/D/G/rho", baton.results, fs, pool));
988   SVN_ERR(expect_unlock("/A/mu", baton.results, fs, pool));
989   SVN_ERR(expect_error("/X/zulu", baton.results, fs, pool));
990 
991   /* Lock again. */
992   apr_hash_clear(baton.results);
993   SVN_ERR(svn_fs_lock_many(fs, lock_paths, "comment", 0, 0, 0,
994                            lock_many_cb, &baton,
995                            pool, pool));
996 
997   SVN_ERR(expect_lock("/A/B/E/alpha", baton.results, fs, pool));
998   SVN_ERR(expect_lock("/A/B/E/beta", baton.results, fs, pool));
999   SVN_ERR(expect_error("/A/B/E/zulu", baton.results, fs, pool));
1000   SVN_ERR(expect_lock("/A/BB/mu", baton.results, fs, pool));
1001   SVN_ERR(expect_lock("/A/BBB/mu", baton.results, fs, pool));
1002   SVN_ERR(expect_lock("/A/D/G/pi", baton.results, fs, pool));
1003   SVN_ERR(expect_lock("/A/D/G/rho", baton.results, fs, pool));
1004   SVN_ERR(expect_lock("/A/mu", baton.results, fs, pool));
1005   SVN_ERR(expect_error("/X/zulu", baton.results, fs, pool));
1006 
1007   /* Unlock without force. */
1008   for (hi = apr_hash_first(pool, baton.results); hi; hi = apr_hash_next(hi))
1009     {
1010       struct lock_result_t *result = apr_hash_this_val(hi);
1011       svn_hash_sets(unlock_paths, apr_hash_this_key(hi),
1012                     result->lock ? result->lock->token : "non-existent-token");
1013     }
1014   apr_hash_clear(baton.results);
1015   SVN_ERR(svn_fs_unlock_many(fs, unlock_paths, FALSE, lock_many_cb, &baton,
1016                              pool, pool));
1017 
1018   SVN_ERR(expect_unlock("/A/B/E/alpha", baton.results, fs, pool));
1019   SVN_ERR(expect_unlock("/A/B/E/beta", baton.results, fs, pool));
1020   SVN_ERR(expect_error("/A/B/E/zulu", baton.results, fs, pool));
1021   SVN_ERR(expect_unlock("/A/BB/mu", baton.results, fs, pool));
1022   SVN_ERR(expect_unlock("/A/BBB/mu", baton.results, fs, pool));
1023   SVN_ERR(expect_unlock("/A/D/G/pi", baton.results, fs, pool));
1024   SVN_ERR(expect_unlock("/A/D/G/rho", baton.results, fs, pool));
1025   SVN_ERR(expect_unlock("/A/mu", baton.results, fs, pool));
1026   SVN_ERR(expect_error("/X/zulu", baton.results, fs, pool));
1027 
1028   return SVN_NO_ERROR;
1029 }
1030 
1031 static svn_error_t *
lock_cb_error(const svn_test_opts_t * opts,apr_pool_t * pool)1032 lock_cb_error(const svn_test_opts_t *opts,
1033               apr_pool_t *pool)
1034 {
1035   svn_fs_t *fs;
1036   svn_revnum_t newrev;
1037   svn_fs_access_t *access;
1038   svn_fs_lock_target_t *target;
1039   struct lock_many_baton_t baton;
1040   apr_hash_t *lock_paths, *unlock_paths;
1041   svn_lock_t *lock;
1042 
1043   SVN_ERR(create_greek_fs(&fs, &newrev, "test-lock-cb-error", opts, pool));
1044   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
1045   SVN_ERR(svn_fs_set_access(fs, access));
1046 
1047   baton.results = apr_hash_make(pool);
1048   baton.pool = pool;
1049   baton.count = 1;
1050   lock_paths = apr_hash_make(pool);
1051   unlock_paths = apr_hash_make(pool);
1052   target = svn_fs_lock_target_create(NULL, newrev, pool);
1053 
1054   svn_hash_sets(lock_paths, "/A/B/E/alpha", target);
1055   svn_hash_sets(lock_paths, "/A/B/E/beta", target);
1056 
1057   apr_hash_clear(baton.results);
1058   SVN_TEST_ASSERT_ERROR(svn_fs_lock_many(fs, lock_paths, "comment", 0, 0, 0,
1059                                          lock_many_cb, &baton,
1060                                          pool, pool),
1061                         SVN_ERR_FS_GENERAL);
1062 
1063   SVN_TEST_ASSERT(apr_hash_count(baton.results) == 1);
1064   SVN_TEST_ASSERT(svn_hash_gets(baton.results, "/A/B/E/alpha")
1065                   || svn_hash_gets(baton.results, "/A/B/E/beta"));
1066   SVN_ERR(svn_fs_get_lock(&lock, fs, "/A/B/E/alpha", pool));
1067   SVN_TEST_ASSERT(lock);
1068   svn_hash_sets(unlock_paths, "/A/B/E/alpha", lock->token);
1069   SVN_ERR(svn_fs_get_lock(&lock, fs, "/A/B/E/beta", pool));
1070   SVN_TEST_ASSERT(lock);
1071   svn_hash_sets(unlock_paths, "/A/B/E/beta", lock->token);
1072 
1073   baton.count = 1;
1074   apr_hash_clear(baton.results);
1075   SVN_TEST_ASSERT_ERROR(svn_fs_unlock_many(fs, unlock_paths, FALSE,
1076                                            lock_many_cb, &baton,
1077                                            pool, pool),
1078                         SVN_ERR_FS_GENERAL);
1079 
1080   SVN_TEST_ASSERT(apr_hash_count(baton.results) == 1);
1081   SVN_TEST_ASSERT(svn_hash_gets(baton.results, "/A/B/E/alpha")
1082                   || svn_hash_gets(baton.results, "/A/B/E/beta"));
1083 
1084   SVN_ERR(svn_fs_get_lock(&lock, fs, "/A/B/E/alpha", pool));
1085   SVN_TEST_ASSERT(!lock);
1086   SVN_ERR(svn_fs_get_lock(&lock, fs, "/A/B/E/beta", pool));
1087   SVN_TEST_ASSERT(!lock);
1088 
1089   return SVN_NO_ERROR;
1090 }
1091 
1092 /* XXX NOTE:
1093    This test will fail on most Unix-like systems when run as the
1094    root user, because flock() will ignore file permissions. */
1095 static svn_error_t *
obtain_write_lock_failure(const svn_test_opts_t * opts,apr_pool_t * pool)1096 obtain_write_lock_failure(const svn_test_opts_t *opts,
1097                           apr_pool_t *pool)
1098 {
1099   svn_fs_t *fs;
1100   svn_revnum_t newrev;
1101   svn_fs_access_t *access;
1102   svn_fs_lock_target_t *target;
1103   struct lock_many_baton_t baton;
1104   apr_hash_t *lock_paths, *unlock_paths;
1105 
1106   /* The test makes sense only for FSFS. */
1107   if (strcmp(opts->fs_type, SVN_FS_TYPE_FSFS) != 0
1108       && strcmp(opts->fs_type, SVN_FS_TYPE_FSX) != 0)
1109     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
1110                             "this will test FSFS/FSX repositories only");
1111 
1112   SVN_ERR(create_greek_fs(&fs, &newrev, "test-obtain-write-lock-failure",
1113                           opts, pool));
1114   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
1115   SVN_ERR(svn_fs_set_access(fs, access));
1116 
1117   /* Make a read only 'write-lock' file.  This prevents any write operations
1118      from being executed. */
1119   SVN_ERR(svn_io_set_file_read_only("test-obtain-write-lock-failure/write-lock",
1120                                     FALSE, pool));
1121 
1122   baton.results = apr_hash_make(pool);
1123   baton.pool = pool;
1124   baton.count = 0;
1125 
1126   /* Trying to lock some paths.  We don't really care about error; the test
1127      shouldn't crash. */
1128   target = svn_fs_lock_target_create(NULL, newrev, pool);
1129   lock_paths = apr_hash_make(pool);
1130   svn_hash_sets(lock_paths, "/iota", target);
1131   svn_hash_sets(lock_paths, "/A/mu", target);
1132 
1133   apr_hash_clear(baton.results);
1134   SVN_TEST_ASSERT_ANY_ERROR(svn_fs_lock_many(fs, lock_paths, "comment", 0, 0, 0,
1135                                              lock_many_cb, &baton, pool, pool));
1136 
1137   /* Trying to unlock some paths.  We don't really care about error; the test
1138      shouldn't crash. */
1139   unlock_paths = apr_hash_make(pool);
1140   svn_hash_sets(unlock_paths, "/iota", "");
1141   svn_hash_sets(unlock_paths, "/A/mu", "");
1142 
1143   apr_hash_clear(baton.results);
1144   SVN_TEST_ASSERT_ANY_ERROR(svn_fs_unlock_many(fs, unlock_paths, TRUE,
1145                                                lock_many_cb, &baton, pool,
1146                                                pool));
1147 
1148   return SVN_NO_ERROR;
1149 }
1150 
1151 static svn_error_t *
parent_and_child_lock(const svn_test_opts_t * opts,apr_pool_t * pool)1152 parent_and_child_lock(const svn_test_opts_t *opts,
1153                       apr_pool_t *pool)
1154 {
1155   svn_fs_t *fs;
1156   svn_fs_access_t *access;
1157   svn_fs_txn_t *txn;
1158   svn_fs_root_t *root;
1159   const char *conflict;
1160   svn_revnum_t newrev;
1161   svn_lock_t *lock;
1162   struct get_locks_baton_t *get_locks_baton;
1163   apr_size_t num_expected_paths;
1164 
1165   SVN_ERR(svn_test__create_fs(&fs, "test-parent-and-child-lock", opts, pool));
1166   SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
1167   SVN_ERR(svn_fs_set_access(fs, access));
1168 
1169   /* Make a file '/A'. */
1170   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
1171   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1172   SVN_ERR(svn_fs_make_file(root, "/A", pool));
1173   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
1174 
1175   /* Obtain a lock on '/A'. */
1176   SVN_ERR(svn_fs_lock(&lock, fs, "/A", NULL, NULL, FALSE, 0, newrev, FALSE,
1177                       pool));
1178 
1179   /* Add a lock token to FS access context. */
1180   SVN_ERR(svn_fs_access_add_lock_token(access, lock->token));
1181 
1182   /* Make some weird change: replace file '/A' by a directory with a
1183      child.  Issue 2507 means that the result is that the directory /A
1184      remains locked. */
1185   SVN_ERR(svn_fs_begin_txn(&txn, fs, newrev, pool));
1186   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1187   SVN_ERR(svn_fs_delete(root, "/A", pool));
1188   SVN_ERR(svn_fs_make_dir(root, "/A", pool));
1189   SVN_ERR(svn_fs_make_file(root, "/A/b", pool));
1190   SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
1191 
1192   /* Obtain a lock on '/A/b'.  Issue 2507 means that the lock index
1193      for / refers to both /A and /A/b, and that the lock index for /A
1194      refers to /A/b. */
1195   SVN_ERR(svn_fs_lock(&lock, fs, "/A/b", NULL, NULL, FALSE, 0, newrev, FALSE,
1196                       pool));
1197 
1198   /* Verify the locked paths. The lock for /A/b should not be reported
1199      twice even though issue 2507 means we access the index for / and
1200      the index for /A both of which refer to /A/b. */
1201   {
1202     static const char *expected_paths[] = {
1203       "/A",
1204       "/A/b",
1205     };
1206     num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
1207     get_locks_baton = make_get_locks_baton(pool);
1208     SVN_ERR(svn_fs_get_locks(fs, "/", get_locks_callback,
1209                              get_locks_baton, pool));
1210     SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
1211                                        num_expected_paths, pool));
1212   }
1213 
1214   return SVN_NO_ERROR;
1215 }
1216 
1217 /* ------------------------------------------------------------------------ */
1218 
1219 /* The test table.  */
1220 
1221 static int max_threads = 2;
1222 
1223 static struct svn_test_descriptor_t test_funcs[] =
1224   {
1225     SVN_TEST_NULL,
1226     SVN_TEST_OPTS_PASS(lock_expiration,
1227                        "test that locks can expire"),
1228     SVN_TEST_OPTS_PASS(lock_only,
1229                        "lock only"),
1230     SVN_TEST_OPTS_PASS(lookup_lock_by_path,
1231                        "lookup lock by path"),
1232     SVN_TEST_OPTS_PASS(attach_lock,
1233                        "attach lock"),
1234     SVN_TEST_OPTS_PASS(get_locks,
1235                        "get locks"),
1236     SVN_TEST_OPTS_PASS(basic_lock,
1237                        "basic locking"),
1238     SVN_TEST_OPTS_PASS(lock_credentials,
1239                        "test that locking requires proper credentials"),
1240     SVN_TEST_OPTS_PASS(final_lock_check,
1241                        "test that locking is enforced in final commit step"),
1242     SVN_TEST_OPTS_PASS(lock_dir_propchange,
1243                        "dir propchange can be committed with locked child"),
1244     SVN_TEST_OPTS_PASS(lock_break_steal_refresh,
1245                        "breaking, stealing, refreshing a lock"),
1246     SVN_TEST_OPTS_PASS(lock_out_of_date,
1247                        "check out-of-dateness before locking"),
1248     SVN_TEST_OPTS_PASS(lock_multiple_paths,
1249                        "lock multiple paths"),
1250     SVN_TEST_OPTS_PASS(lock_cb_error,
1251                        "lock callback error"),
1252     SVN_TEST_OPTS_PASS(obtain_write_lock_failure,
1253                        "lock/unlock when 'write-lock' couldn't be obtained"),
1254     SVN_TEST_OPTS_PASS(parent_and_child_lock,
1255                        "lock parent and it's child"),
1256     SVN_TEST_NULL
1257   };
1258 
1259 SVN_TEST_MAIN
1260