// 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, 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_NoRandomSave) {
  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));

  // Register a signal handler for SIGALRM and set an alarm that will go off
  // while blocking in the subsequent flock() call. This will interrupt flock()
  // and cause it to return EINTR.
  struct sigaction act = {};
  act.sa_handler = trivial_handler;
  ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds());
  ASSERT_THAT(ualarm(10000, 0), SyscallSucceeds());
  ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallFailsWithErrno(EINTR));

  // 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_NoRandomSave) {
  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));

  // Register a signal handler for SIGALRM and set an alarm that will go off
  // while blocking in the subsequent flock() call. This will interrupt flock()
  // and cause it to return EINTR.
  struct sigaction act = {};
  act.sa_handler = trivial_handler;
  ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds());
  ASSERT_THAT(ualarm(10000, 0), SyscallSucceeds());
  ASSERT_THAT(flock(fd.get(), LOCK_EX), SyscallFailsWithErrno(EINTR));

  // 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