/* lock-test.c --- tests for the filesystem locking functions * * ==================================================================== * Copyright (c) 2000-2004 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */ #include #include #include #include "svn_error.h" #include "svn_fs.h" #include "../svn_test_fs.h" /*-----------------------------------------------------------------*/ /** Helper functions **/ /* Implementations of the svn_fs_get_locks_callback_t interface and baton, for verifying expected output from svn_fs_get_locks(). */ struct get_locks_baton_t { apr_hash_t *locks; }; static svn_error_t * get_locks_callback(void *baton, svn_lock_t *lock, apr_pool_t *pool) { struct get_locks_baton_t *b = baton; apr_pool_t *hash_pool = apr_hash_pool_get(b->locks); svn_string_t *lock_path = svn_string_create(lock->path, hash_pool); apr_hash_set(b->locks, lock_path->data, lock_path->len, svn_lock_dup(lock, hash_pool)); return SVN_NO_ERROR; } /* A factory function. */ static struct get_locks_baton_t * make_get_locks_baton(apr_pool_t *pool) { struct get_locks_baton_t *baton = apr_pcalloc(pool, sizeof(*baton)); baton->locks = apr_hash_make(pool); return baton; } /* And verification function(s). */ 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) { apr_size_t i; if (num_expected_paths != apr_hash_count(baton->locks)) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Unexpected number of locks."); for (i = 0; i < num_expected_paths; i++) { const char *path = expected_paths[i]; if (! apr_hash_get(baton->locks, path, APR_HASH_KEY_STRING)) return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, "Missing lock for path '%s'", path); } return SVN_NO_ERROR; } /*-----------------------------------------------------------------*/ /** The actual lock-tests called by `make check` **/ /* Test that we can create a lock--nothing more. */ static svn_error_t * lock_only(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; *msg = "lock only"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-only", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Lock /A/D/G/rho. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); return SVN_NO_ERROR; } /* Test that we can create, fetch, and destroy a lock. It exercises each of the five public fs locking functions. */ static svn_error_t * lookup_lock_by_path(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock, *somelock; *msg = "lookup lock by path"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lookup-lock-by-path", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Lock /A/D/G/rho. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* Can we look up the lock by path? */ SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0)) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Couldn't look up a lock by pathname."); return SVN_NO_ERROR; } /* Test that we can create a lock outside of the fs and attach it to a path. */ static svn_error_t * attach_lock(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *somelock; svn_lock_t *mylock; const char *token; *msg = "attach lock"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-attach-lock", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_generate_lock_token(&token, fs, pool)); SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", token, "This is a comment. Yay comment!", 0, apr_time_now() + apr_time_from_sec(3), SVN_INVALID_REVNUM, FALSE, pool)); /* Can we look up the lock by path? */ SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0)) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Couldn't look up a lock by pathname."); /* Unlock /A/D/G/rho, and verify that it's gone. */ SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 0, pool)); SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if (somelock) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Removed a lock, but it's still there."); return SVN_NO_ERROR; } /* Test that we can get all locks under a directory. */ static svn_error_t * get_locks(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; struct get_locks_baton_t *get_locks_baton; apr_size_t i, num_expected_paths; *msg = "get locks"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-get-locks", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Lock our paths; verify from "/". */ { static const char *expected_paths[] = { "/A/D/G/pi", "/A/D/G/rho", "/A/D/G/tau", "/A/D/H/psi", "/A/D/H/chi", "/A/D/H/omega", "/A/B/E/alpha", "/A/B/E/beta", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); for (i = 0; i < num_expected_paths; i++) { SVN_ERR(svn_fs_lock(&mylock, fs, expected_paths[i], NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); } get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /* Verify from "/A/B". */ { static const char *expected_paths[] = { "/A/B/E/alpha", "/A/B/E/beta", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "A/B", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /* Verify from "/A/D". */ { static const char *expected_paths[] = { "/A/D/G/pi", "/A/D/G/rho", "/A/D/G/tau", "/A/D/H/psi", "/A/D/H/chi", "/A/D/H/omega", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "A/D", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /* Verify from "/A/D/G". */ { static const char *expected_paths[] = { "/A/D/G/pi", "/A/D/G/rho", "/A/D/G/tau", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "A/D/G", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /* Verify from "/A/D/H/omega". */ { static const char *expected_paths[] = { "/A/D/H/omega", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "A/D/H/omega", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /* Verify from "/iota" (which wasn't locked... tricky...). */ { static const char *expected_paths[] = { 0 }; num_expected_paths = 0; get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "iota", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } return SVN_NO_ERROR; } /* Test that we can create, fetch, and destroy a lock. It exercises each of the five public fs locking functions. */ static svn_error_t * basic_lock(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock, *somelock; *msg = "basic locking"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-lock", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Lock /A/D/G/rho. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* Can we look up the lock by path? */ SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0)) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Couldn't look up a lock by pathname."); /* Unlock /A/D/G/rho, and verify that it's gone. */ SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 0, pool)); SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if (somelock) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Removed a lock, but it's still there."); return SVN_NO_ERROR; } /* Test that locks are enforced -- specifically that both a username and token are required to make use of the lock. */ static svn_error_t * lock_credentials(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; svn_error_t *err; *msg = "test that locking requires proper credentials"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-credentials", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Lock /A/D/G/rho. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* Push the proper lock-token into the fs access context. */ SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token)); /* Make a new transaction and change rho. */ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho", "new contents", pool)); /* We are no longer 'bubba'. We're nobody. */ SVN_ERR(svn_fs_set_access(fs, NULL)); /* Try to commit the file change. Should fail, because we're nobody. */ err = svn_fs_commit_txn(&conflict, &newrev, txn, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to commit locked file without any fs username."); svn_error_clear(err); /* We are now 'hortense'. */ SVN_ERR(svn_fs_create_access(&access, "hortense", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Try to commit the file change. Should fail, because we're 'hortense'. */ err = svn_fs_commit_txn(&conflict, &newrev, txn, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to commit locked file as non-owner."); svn_error_clear(err); /* Be 'bubba' again. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Try to commit the file change. Should fail, because there's no token. */ err = svn_fs_commit_txn(&conflict, &newrev, txn, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to commit locked file with no lock token."); svn_error_clear(err); /* Push the proper lock-token into the fs access context. */ SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token)); /* Commit should now succeed. */ SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); return SVN_NO_ERROR; } /* Test that locks are enforced at commit time. Somebody might lock something behind your back, right before you run svn_fs_commit_txn(). Also, this test verifies that recursive lock-checks on directories is working properly. */ static svn_error_t * final_lock_check(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; svn_error_t *err; *msg = "test that locking is enforced in final commit step"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-final-lock-check", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* Make a new transaction and delete "/A" */ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_fs_delete(txn_root, "/A", pool)); /* Become 'bubba' and lock "/A/D/G/rho". */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* We are no longer 'bubba'. We're nobody. */ SVN_ERR(svn_fs_set_access(fs, NULL)); /* Try to commit the transaction. Should fail, because a child of the deleted directory is locked by someone else. */ err = svn_fs_commit_txn(&conflict, &newrev, txn, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to commit dir deletion when a child is locked."); svn_error_clear(err); /* Supply correct username and token; commit should work. */ SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); return SVN_NO_ERROR; } /* If a directory's child is locked by someone else, we should still be able to commit a propchange on the directory. */ static svn_error_t * lock_dir_propchange(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; *msg = "dir propchange can be committed with locked child"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-dir-propchange", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* Become 'bubba' and lock "/A/D/G/rho". */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* We are no longer 'bubba'. We're nobody. */ SVN_ERR(svn_fs_set_access(fs, NULL)); /* Make a new transaction and make a propchange on "/A" */ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_fs_change_node_prop(txn_root, "/A", "foo", svn_string_create("bar", pool), pool)); /* Commit should succeed; this means we're doing a non-recursive lock-check on directory, rather than a recursive one. */ SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); return SVN_NO_ERROR; } /* DAV clients sometimes LOCK non-existent paths, as a way of reserving names. Check that this technique works. */ static svn_error_t * lock_name_reservation(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root, *rev_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; svn_error_t *err; *msg = "able to reserve a name (lock non-existent path)"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-name-reservation", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* Become 'bubba' and lock imaginary path "/A/D/G2/blooga". */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G2/blooga", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* We are no longer 'bubba'. We're nobody. */ SVN_ERR(svn_fs_set_access(fs, NULL)); /* Make a new transaction. */ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* This copy should fail, because an imaginary path in the target of the copy is reserved by someone else. */ SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, pool)); err = svn_fs_copy(rev_root, "/A/D/G", txn_root, "/A/D/G2", pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, copy succeeded when path within target was locked."); svn_error_clear(err); return SVN_NO_ERROR; } /* Test that we can set and get locks in and under a directory. We'll use non-existent FS paths for this test, though, as the FS API currently disallows directory locking. */ static svn_error_t * directory_locks_kinda(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; svn_fs_access_t *access; svn_lock_t *mylock; apr_size_t num_expected_paths, i; struct get_locks_baton_t *get_locks_baton; *msg = "directory locks (kinda)"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-directory-locks-kinda", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /*** Lock some various, non-existent, yet dir-name-spacily overlapping paths; verify. ***/ { static const char *expected_paths[] = { "/Program Files/Tigris.org/Subversion", "/Program Files/Tigris.org", "/Stuff/Junk/Fluff", "/Program Files", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); /* Lock all paths under /A/D/G. */ for (i = 0; i < num_expected_paths; i++) { SVN_ERR(svn_fs_lock(&mylock, fs, expected_paths[i], NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); } get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "/", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /*** Now unlock a "middle directory" ***/ { static const char *expected_paths[] = { "/Program Files/Tigris.org/Subversion", "/Stuff/Junk/Fluff", "/Program Files", }; num_expected_paths = sizeof(expected_paths) / sizeof(const char *); SVN_ERR(svn_fs_unlock(fs, "/Program Files/Tigris.org", NULL, TRUE, pool)); get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "/", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } return SVN_NO_ERROR; } /* Test that locks auto-expire correctly. */ static svn_error_t * lock_expiration(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; svn_error_t *err; struct get_locks_baton_t *get_locks_baton; *msg = "test that locks can expire"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-expiration", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* Make a new transaction and change rho. */ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho", "new contents", pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Lock /A/D/G/rho, with an expiration 3 seconds from now. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, apr_time_now() + apr_time_from_sec(3), SVN_INVALID_REVNUM, FALSE, pool)); /* Become nobody. */ SVN_ERR(svn_fs_set_access(fs, NULL)); /* Try to commit. Should fail because we're 'nobody', and the lock hasn't expired yet. */ err = svn_fs_commit_txn(&conflict, &newrev, txn, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to commit a file that has a non-expired lock."); svn_error_clear(err); /* Check that the lock is there, by getting it via the paths parent. */ { static const char *expected_paths [] = { "/A/D/G/rho" }; apr_size_t num_expected_paths = (sizeof(expected_paths) / sizeof(expected_paths[0])); get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "/A/D/G", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } /* Sleep 5 seconds, so the lock auto-expires. Anonymous commit should then succeed. */ apr_sleep(apr_time_from_sec(5)); /* Verify that the lock auto-expired even in the recursive case. */ { static const char *expected_paths [] = { 0 }; apr_size_t num_expected_paths = 0; get_locks_baton = make_get_locks_baton(pool); SVN_ERR(svn_fs_get_locks(fs, "/A/D/G", get_locks_callback, get_locks_baton, pool)); SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths, num_expected_paths, pool)); } SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); return SVN_NO_ERROR; } /* Test that a lock can be broken, stolen, or refreshed */ static svn_error_t * lock_break_steal_refresh(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock, *somelock; *msg = "breaking, stealing, refreshing a lock"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-steal-refresh", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* Become 'bubba' and lock "/A/D/G/rho". */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); /* Become 'hortense' and break bubba's lock, then verify it's gone. */ SVN_ERR(svn_fs_create_access(&access, "hortense", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 1 /* FORCE BREAK */, pool)); SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if (somelock) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Tried to break a lock, but it's still there."); /* As hortense, create a new lock, and verify that we own it. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, SVN_INVALID_REVNUM, FALSE, pool)); SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if (strcmp(somelock->owner, mylock->owner) != 0) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Made a lock, but we don't seem to own it."); /* As bubba, steal hortense's lock, creating a new one that expires. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, apr_time_now() + apr_time_from_sec(300), /* 5 min. */ SVN_INVALID_REVNUM, TRUE /* FORCE STEAL */, pool)); SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if (strcmp(somelock->owner, mylock->owner) != 0) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Made a lock, but we don't seem to own it."); if (! somelock->expiration_date) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Made expiring lock, but seems not to expire."); /* Refresh the lock, so that it never expires. */ SVN_ERR(svn_fs_lock(&somelock, fs, somelock->path, somelock->token, somelock->comment, 0, 0, SVN_INVALID_REVNUM, TRUE /* FORCE STEAL */, pool)); SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool)); if (somelock->expiration_date) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Made non-expirirng lock, but it expires."); return SVN_NO_ERROR; } /* Test that svn_fs_lock() and svn_fs_attach_lock() can do out-of-dateness checks.. */ static svn_error_t * lock_out_of_date(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict; svn_revnum_t newrev; svn_fs_access_t *access; svn_lock_t *mylock; svn_error_t *err; *msg = "check out-of-dateness before locking"; if (msg_only) return SVN_NO_ERROR; /* Prepare a filesystem and a new txn. */ SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-out-of-date", opts->fs_type, pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); /* Create the greek tree and commit it. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* Commit a small change to /A/D/G/rho, creating revision 2. */ SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho", "new contents", pool)); SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool)); /* We are now 'bubba'. */ SVN_ERR(svn_fs_create_access(&access, "bubba", pool)); SVN_ERR(svn_fs_set_access(fs, access)); /* Try to lock /A/D/G/rho, but claim that we still have r1 of the file. */ err = svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, 1, FALSE, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to lock an out-of-date file."); svn_error_clear(err); /* Attempt lock again, this time claiming to have r2. */ SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, 2, FALSE, pool)); /* 'Refresh' the lock, claiming to have r1... should fail. */ err = svn_fs_lock(&mylock, fs, mylock->path, mylock->token, mylock->comment, 0, apr_time_now() + apr_time_from_sec(50), 1, TRUE /* FORCE STEAL */, pool); if (! err) return svn_error_create (SVN_ERR_TEST_FAILED, NULL, "Uhoh, able to refresh a lock on an out-of-date file."); svn_error_clear(err); return SVN_NO_ERROR; } /* ------------------------------------------------------------------------ */ /* The test table. */ struct svn_test_descriptor_t test_funcs[] = { SVN_TEST_NULL, SVN_TEST_PASS(lock_only), SVN_TEST_PASS(lookup_lock_by_path), SVN_TEST_PASS(attach_lock), SVN_TEST_PASS(get_locks), SVN_TEST_PASS(basic_lock), SVN_TEST_PASS(lock_credentials), SVN_TEST_PASS(final_lock_check), SVN_TEST_PASS(lock_dir_propchange), SVN_TEST_XFAIL(lock_name_reservation), SVN_TEST_XFAIL(directory_locks_kinda), SVN_TEST_PASS(lock_expiration), SVN_TEST_PASS(lock_break_steal_refresh), SVN_TEST_PASS(lock_out_of_date), SVN_TEST_NULL };