/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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 <cstdlib>
#include <mutex>

#include <boost/thread/locks.hpp>
#include <glog/logging.h>

#include <folly/File.h>
#include <folly/String.h>
#include <folly/Subprocess.h>
#include <folly/experimental/TestUtil.h>
#include <folly/experimental/io/FsUtil.h>
#include <folly/portability/GFlags.h>
#include <folly/portability/GTest.h>

using namespace folly;
using namespace folly::test;

DEFINE_bool(s, false, "get shared lock");
DEFINE_bool(x, false, "get exclusive lock");

TEST(File, Locks) {
  typedef std::unique_lock<File> Lock;
  typedef boost::shared_lock<File> SharedLock;

  // Find out where we are.
  static constexpr size_t pathLength = 2048;
  char buf[pathLength + 1];
  int r = readlink("/proc/self/exe", buf, pathLength);
  CHECK(r != -1);
  buf[r] = '\0';

  fs::path helper;
  const auto* envPath = getenv("FOLLY_FILE_LOCK_TEST_HELPER");
  if (envPath) {
    helper = envPath;
  } else {
    const fs::path me(buf);
    const auto helper_basename = "file_test_lock_helper";
    helper = me.parent_path() / helper_basename;
  }
  if (!fs::exists(helper)) {
    throw std::runtime_error(
        folly::to<std::string>("cannot find helper ", helper.string()));
  }

  TemporaryFile tempFile;
  File f(tempFile.fd());

  enum LockMode { EXCLUSIVE, SHARED };
  auto testLock = [&](LockMode mode, bool expectedSuccess) {
    auto ret = Subprocess({helper.string(),
                           mode == SHARED ? "-s" : "-x",
                           tempFile.path().string()})
                   .wait();
    EXPECT_TRUE(ret.exited());
    if (ret.exited()) {
      EXPECT_EQ(expectedSuccess ? 0 : 42, ret.exitStatus());
    }
  };

  // Make sure nothing breaks and things compile.
  { Lock lock(f); }

  { SharedLock lock(f); }

  {
    Lock lock(f, std::defer_lock);
    EXPECT_TRUE(lock.try_lock());
  }

  {
    SharedLock lock(f, boost::defer_lock);
    EXPECT_TRUE(lock.try_lock());
  }

  // X blocks X
  {
    Lock lock(f);
    testLock(EXCLUSIVE, false);
  }

  // X blocks S
  {
    Lock lock(f);
    testLock(SHARED, false);
  }

  // S blocks X
  {
    SharedLock lock(f);
    testLock(EXCLUSIVE, false);
  }

  // S does not block S
  {
    SharedLock lock(f);
    testLock(SHARED, true);
  }
}
