summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/flock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/flock.cc')
-rw-r--r--test/syscalls/linux/flock.cc636
1 files changed, 636 insertions, 0 deletions
diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc
new file mode 100644
index 000000000..638a93979
--- /dev/null
+++ b/test/syscalls/linux/flock.cc
@@ -0,0 +1,636 @@
+// 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 <errno.h>
+#include <sys/file.h>
+
+#include <string>
+
+#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, TestSharedLockFailExclusiveHolder) {
+ // 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));
+}
+
+TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolder) {
+ // 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, 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_NoRandomSave) {
+ // 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_NoRandomSave) {
+ // 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_NoRandomSave) {
+ // 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_NoRandomSave) {
+ // 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<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallSucceeds());
+
+ EXPECT_THAT(flock(sock, LOCK_EX | LOCK_NB), SyscallSucceeds());
+ EXPECT_THAT(close(sock), SyscallSucceeds());
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor