// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include "gtest/gtest.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/syscalls/linux/file_base.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/util/file_descriptor.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" #include "test/util/timer_util.h" namespace gvisor { namespace testing { namespace { class FlockTest : public FileTest {}; TEST_F(FlockTest, InvalidOpCombinations) { // The operation cannot be both exclusive and shared. EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_SH | LOCK_NB), SyscallFailsWithErrno(EINVAL)); // Locking and Unlocking doesn't make sense. EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_UN | LOCK_NB), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_UN | LOCK_NB), SyscallFailsWithErrno(EINVAL)); } TEST_F(FlockTest, NoOperationSpecified) { // Not specifying an operation is invalid. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB), SyscallFailsWithErrno(EINVAL)); } TEST_F(FlockTest, TestSimpleExLock) { // Test that we can obtain an exclusive lock (no other holders) // and that we can unlock it. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestSimpleShLock) { // Test that we can obtain a shared lock (no other holders) // and that we can unlock it. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestLockableAnyMode) { // flock(2): A shared or exclusive lock can be placed on a file // regardless of the mode in which the file was opened. const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Open(test_file_name_, O_RDONLY)); // open read only to test // Mode shouldn't prevent us from taking an exclusive lock. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); // Unlock ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestUnlockWithNoHolders) { // Test that unlocking when no one holds a lock succeeeds. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestRepeatedExLockingBySameHolder) { // Test that repeated locking by the same holder for the // same type of lock works correctly. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestRepeatedExLockingSingleUnlock) { // Test that repeated locking by the same holder for the // same type of lock works correctly and that a single unlock is required. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); // Should be unlocked at this point ASSERT_THAT(flock(fd.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestRepeatedShLockingBySameHolder) { // Test that repeated locking by the same holder for the // same type of lock works correctly. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestSingleHolderUpgrade) { // Test that a shared lock is upgradable when no one else holds a lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestSingleHolderDowngrade) { // Test single holder lock downgrade case. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestMultipleShared) { // This is a simple test to verify that multiple independent shared // locks will be granted. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // A shared lock should be granted as there only exists other shared locks. ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); // Unlock both. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } /* * flock(2): If a process uses open(2) (or similar) to obtain more than one * descriptor for the same file, these descriptors are treated * independently by flock(). An attempt to lock the file using one of * these file descriptors may be denied by a lock that the calling process * has already placed via another descriptor. */ TEST_F(FlockTest, TestMultipleHolderSharedExclusive) { // This test will verify that an exclusive lock will not be granted // while a shared is held. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Verify We're unable to get an exlcusive lock via the second FD. // because someone is holding a shared lock. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestSharedLockFailExclusiveHolderNonblocking) { // This test will verify that a shared lock is denied while // someone holds an exclusive lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Verify we're unable to get an shared lock via the second FD. // because someone is holding an exclusive lock. ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } void trivial_handler(int signum) {} TEST_F(FlockTest, TestSharedLockFailExclusiveHolderBlocking) { const DisableSave ds; // Timing-related. // This test will verify that a shared lock is denied while // someone holds an exclusive lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Make sure that a blocking flock() call will return EINTR when interrupted // by a signal. Create a timer that will go off while blocking on flock(), and // register the corresponding signal handler. auto timer = ASSERT_NO_ERRNO_AND_VALUE( TimerCreate(CLOCK_MONOTONIC, sigevent_t{ .sigev_signo = SIGALRM, .sigev_notify = SIGEV_SIGNAL, })); struct sigaction act = {}; act.sa_handler = trivial_handler; ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); // Now that the signal handler is registered, set the timer. Set an interval // so that it's ok if the timer goes off before we call flock. ASSERT_NO_ERRNO( timer.Set(0, itimerspec{ .it_interval = absl::ToTimespec(absl::Milliseconds(10)), .it_value = absl::ToTimespec(absl::Milliseconds(10)), })); ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallFailsWithErrno(EINTR)); timer.reset(); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolderNonblocking) { // This test will verify that an exclusive lock is denied while // someone already holds an exclsuive lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Verify we're unable to get an exclusive lock via the second FD // because someone is already holding an exclusive lock. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolderBlocking) { const DisableSave ds; // Timing-related. // This test will verify that an exclusive lock is denied while // someone already holds an exclsuive lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Make sure that a blocking flock() call will return EINTR when interrupted // by a signal. Create a timer that will go off while blocking on flock(), and // register the corresponding signal handler. auto timer = ASSERT_NO_ERRNO_AND_VALUE( TimerCreate(CLOCK_MONOTONIC, sigevent_t{ .sigev_signo = SIGALRM, .sigev_notify = SIGEV_SIGNAL, })); struct sigaction act = {}; act.sa_handler = trivial_handler; ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); // Now that the signal handler is registered, set the timer. Set an interval // so that it's ok if the timer goes off before we call flock. ASSERT_NO_ERRNO( timer.Set(0, itimerspec{ .it_interval = absl::ToTimespec(absl::Milliseconds(10)), .it_value = absl::ToTimespec(absl::Milliseconds(10)), })); ASSERT_THAT(flock(fd.get(), LOCK_EX), SyscallFailsWithErrno(EINTR)); timer.reset(); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestMultipleHolderSharedExclusiveUpgrade) { // This test will verify that we cannot obtain an exclusive lock while // a shared lock is held by another descriptor, then verify that an upgrade // is possible on a shared lock once all other shared locks have closed. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Verify we're unable to get an exclusive lock via the second FD because // a shared lock is held. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Verify that we can get a shared lock via the second descriptor instead ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); // Unlock the first and there will only be one shared lock remaining. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); // Upgrade 2nd fd. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); // Finally unlock the second ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestMultipleHolderSharedExclusiveDowngrade) { // This test will verify that a shared lock is not obtainable while an // exclusive lock is held but that once the first is downgraded that // the second independent file descriptor can also get a shared lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Verify We're unable to get a shared lock via the second FD because // an exclusive lock is held. ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Verify that we can downgrade the first. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); // Now verify that we can obtain a shared lock since the first was downgraded. ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); // Finally unlock both. ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } /* * flock(2): Locks created by flock() are associated with an open file table * entry. This means that duplicate file descriptors (created by, for example, * fork(2) or dup(2)) refer to the same lock, and this lock may be modified or * released using any of these descriptors. Furthermore, the lock is released * either by an explicit LOCK_UN operation on any of these duplicate descriptors * or when all such descriptors have been closed. */ TEST_F(FlockTest, TestDupFdUpgrade) { // This test will verify that a shared lock is upgradeable via a dupped // file descriptor, if the FD wasn't dupped this would fail. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); // Now we should be able to upgrade via the dupped fd. ASSERT_THAT(flock(dup_fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); // Validate unlock via dupped fd. ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestDupFdDowngrade) { // This test will verify that a exclusive lock is downgradable via a dupped // file descriptor, if the FD wasn't dupped this would fail. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); // Now we should be able to downgrade via the dupped fd. ASSERT_THAT(flock(dup_fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); // Validate unlock via dupped fd ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestDupFdCloseRelease) { // flock(2): Furthermore, the lock is released either by an explicit LOCK_UN // operation on any of these duplicate descriptors, or when all such // descriptors have been closed. // // This test will verify that a dupped fd closing will not release the // underlying lock until all such dupped fds have closed. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); // At this point we have ONE exclusive locked referenced by two different fds. const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Validate that we cannot get a lock on a new unrelated FD. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Closing the dupped fd shouldn't affect the lock until all are closed. dup_fd.reset(); // Closed the duped fd. // Validate that we still cannot get a lock on a new unrelated FD. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Closing the first fd CloseFile(); // Will validate the syscall succeeds. // Now we should actually be able to get a lock since all fds related to // the first lock are closed. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); // Unlock. ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestDupFdUnlockRelease) { /* flock(2): Furthermore, the lock is released either by an explicit LOCK_UN * operation on any of these duplicate descriptors, or when all such * descriptors have been closed. */ // This test will verify that an explict unlock on a dupped FD will release // the underlying lock unlike the previous case where close on a dup was // not enough to release the lock. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); // At this point we have ONE exclusive locked referenced by two different fds. const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Validate that we cannot get a lock on a new unrelated FD. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Explicitly unlock via the dupped descriptor. ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); // Validate that we can now get the lock since we explicitly unlocked. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); // Unlock ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } TEST_F(FlockTest, TestDupFdFollowedByLock) { // This test will verify that taking a lock on a file descriptor that has // already been dupped means that the lock is shared between both. This is // slightly different than than duping on an already locked FD. FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); // Take a lock. ASSERT_THAT(flock(dup_fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); // Now dup_fd and test_file_ should both reference the same lock. // We shouldn't be able to obtain a lock until both are closed. const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Closing the first fd dup_fd.reset(); // Close the duped fd. // Validate that we cannot get a lock yet because the dupped descriptor. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EWOULDBLOCK)); // Closing the second fd. CloseFile(); // CloseFile() will validate the syscall succeeds. // Now we should be able to get the lock. ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); // Unlock. ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); } // NOTE: These blocking tests are not perfect. Unfortunately it's very hard to // determine if a thread was actually blocked in the kernel so we're forced // to use timing. TEST_F(FlockTest, BlockingLockNoBlockingForSharedLocks) { // This test will verify that although LOCK_NB isn't specified // two different fds can obtain shared locks without blocking. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH), SyscallSucceeds()); // kHoldLockTime is the amount of time we will hold the lock before releasing. constexpr absl::Duration kHoldLockTime = absl::Seconds(30); const DisableSave ds; // Timing-related. // We do this in another thread so we can determine if it was actually // blocked by timing the amount of time it took for the syscall to complete. ScopedThread t([&] { MonotonicTimer timer; const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // Only a single shared lock is held, the lock will be granted immediately. // This should be granted without any blocking. Don't save here to avoid // wild discrepencies on timing. timer.Start(); ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallSucceeds()); // We held the lock for 30 seconds but this thread should not have // blocked at all so we expect a very small duration on syscall completion. ASSERT_LT(timer.Duration(), absl::Seconds(1)); // 1000ms is much less than 30s. // We can release our second shared lock ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); }); // Sleep before unlocking. absl::SleepFor(kHoldLockTime); // Release the first shared lock. Don't save in this situation to avoid // discrepencies in timing. EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); } TEST_F(FlockTest, BlockingLockFirstSharedSecondExclusive) { // This test will verify that if someone holds a shared lock any attempt to // obtain an exclusive lock will result in blocking. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH), SyscallSucceeds()); // kHoldLockTime is the amount of time we will hold the lock before releasing. constexpr absl::Duration kHoldLockTime = absl::Seconds(2); const DisableSave ds; // Timing-related. // We do this in another thread so we can determine if it was actually // blocked by timing the amount of time it took for the syscall to complete. ScopedThread t([&] { MonotonicTimer timer; const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // This exclusive lock should block because someone is already holding a // shared lock. We don't save here to avoid wild discrepencies on timing. timer.Start(); ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_EX), SyscallSucceeds()); // We should be blocked, we will expect to be blocked for more than 1.0s. ASSERT_GT(timer.Duration(), absl::Seconds(1)); // We can release our exclusive lock. ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); }); // Sleep before unlocking. absl::SleepFor(kHoldLockTime); // Release the shared lock allowing the thread to proceed. // We don't save here to avoid wild discrepencies in timing. EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); } TEST_F(FlockTest, BlockingLockFirstExclusiveSecondShared) { // This test will verify that if someone holds an exclusive lock any attempt // to obtain a shared lock will result in blocking. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX), SyscallSucceeds()); // kHoldLockTime is the amount of time we will hold the lock before releasing. constexpr absl::Duration kHoldLockTime = absl::Seconds(2); const DisableSave ds; // Timing-related. // We do this in another thread so we can determine if it was actually // blocked by timing the amount of time it took for the syscall to complete. ScopedThread t([&] { MonotonicTimer timer; const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // This shared lock should block because someone is already holding an // exclusive lock. We don't save here to avoid wild discrepencies on timing. timer.Start(); ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_SH), SyscallSucceeds()); // We should be blocked, we will expect to be blocked for more than 1.0s. ASSERT_GT(timer.Duration(), absl::Seconds(1)); // We can release our shared lock. ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); }); // Sleep before unlocking. absl::SleepFor(kHoldLockTime); // Release the exclusive lock allowing the blocked thread to proceed. // We don't save here to avoid wild discrepencies in timing. EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); } TEST_F(FlockTest, BlockingLockFirstExclusiveSecondExclusive) { // This test will verify that if someone holds an exclusive lock any attempt // to obtain another exclusive lock will result in blocking. ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX), SyscallSucceeds()); // kHoldLockTime is the amount of time we will hold the lock before releasing. constexpr absl::Duration kHoldLockTime = absl::Seconds(2); const DisableSave ds; // Timing-related. // We do this in another thread so we can determine if it was actually // blocked by timing the amount of time it took for the syscall to complete. ScopedThread t([&] { MonotonicTimer timer; const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); // This exclusive lock should block because someone is already holding an // exclusive lock. timer.Start(); ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_EX), SyscallSucceeds()); // We should be blocked, we will expect to be blocked for more than 1.0s. ASSERT_GT(timer.Duration(), absl::Seconds(1)); // We can release our exclusive lock. ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); }); // Sleep before unlocking. absl::SleepFor(kHoldLockTime); // Release the exclusive lock allowing the blocked thread to proceed. // We don't save to avoid wild discrepencies in timing. EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); } TEST(FlockTestNoFixture, BadFD) { // EBADF: fd is not an open file descriptor. ASSERT_THAT(flock(-1, 0), SyscallFailsWithErrno(EBADF)); } TEST(FlockTestNoFixture, FlockDir) { auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0000)); EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); } TEST(FlockTestNoFixture, FlockSymlink) { // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH // is supported. SKIP_IF(IsRunningOnGvisor()); auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); auto symlink = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path())); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(symlink.path(), O_RDONLY | O_PATH, 0000)); EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EBADF)); } TEST(FlockTestNoFixture, FlockProc) { auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/status", O_RDONLY, 0000)); EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); } TEST(FlockTestNoFixture, FlockPipe) { int fds[2]; ASSERT_THAT(pipe(fds), SyscallSucceeds()); EXPECT_THAT(flock(fds[0], LOCK_EX | LOCK_NB), SyscallSucceeds()); // Check that the pipe was locked above. EXPECT_THAT(flock(fds[1], LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EAGAIN)); EXPECT_THAT(flock(fds[0], LOCK_UN), SyscallSucceeds()); EXPECT_THAT(flock(fds[1], LOCK_EX | LOCK_NB), SyscallSucceeds()); EXPECT_THAT(close(fds[0]), SyscallSucceeds()); EXPECT_THAT(close(fds[1]), SyscallSucceeds()); } TEST(FlockTestNoFixture, FlockSocket) { int sock = socket(AF_UNIX, SOCK_STREAM, 0); ASSERT_THAT(sock, SyscallSucceeds()); struct sockaddr_un addr = ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true /* abstract */, AF_UNIX)); ASSERT_THAT( bind(sock, reinterpret_cast(&addr), sizeof(addr)), SyscallSucceeds()); EXPECT_THAT(flock(sock, LOCK_EX | LOCK_NB), SyscallSucceeds()); EXPECT_THAT(close(sock), SyscallSucceeds()); } } // namespace } // namespace testing } // namespace gvisor