env_posix_test.cc raw

   1  // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style license that can be
   3  // found in the LICENSE file. See the AUTHORS file for names of contributors.
   4  
   5  #include <sys/resource.h>
   6  #include <sys/wait.h>
   7  #include <unistd.h>
   8  
   9  #include <cstdio>
  10  #include <cstdlib>
  11  #include <cstring>
  12  #include <string>
  13  #include <unordered_set>
  14  #include <vector>
  15  
  16  #include "leveldb/env.h"
  17  #include "port/port.h"
  18  #include "util/env_posix_test_helper.h"
  19  #include "util/testharness.h"
  20  
  21  #if HAVE_O_CLOEXEC
  22  
  23  namespace {
  24  
  25  // Exit codes for the helper process spawned by TestCloseOnExec* tests.
  26  // Useful for debugging test failures.
  27  constexpr int kTextCloseOnExecHelperExecFailedCode = 61;
  28  constexpr int kTextCloseOnExecHelperDup2FailedCode = 62;
  29  constexpr int kTextCloseOnExecHelperFoundOpenFdCode = 63;
  30  
  31  // Global set by main() and read in TestCloseOnExec.
  32  //
  33  // The argv[0] value is stored in a std::vector instead of a std::string because
  34  // std::string does not return a mutable pointer to its buffer until C++17.
  35  //
  36  // The vector stores the string pointed to by argv[0], plus the trailing null.
  37  std::vector<char>* GetArgvZero() {
  38    static std::vector<char> program_name;
  39    return &program_name;
  40  }
  41  
  42  // Command-line switch used to run this test as the CloseOnExecSwitch helper.
  43  static const char kTestCloseOnExecSwitch[] = "--test-close-on-exec-helper";
  44  
  45  // Executed in a separate process by TestCloseOnExec* tests.
  46  //
  47  // main() delegates to this function when the test executable is launched with
  48  // a special command-line switch. TestCloseOnExec* tests fork()+exec() the test
  49  // executable and pass the special command-line switch.
  50  //
  51  
  52  // main() delegates to this function when the test executable is launched with
  53  // a special command-line switch. TestCloseOnExec* tests fork()+exec() the test
  54  // executable and pass the special command-line switch.
  55  //
  56  // When main() delegates to this function, the process probes whether a given
  57  // file descriptor is open, and communicates the result via its exit code.
  58  int TestCloseOnExecHelperMain(char* pid_arg) {
  59    int fd = std::atoi(pid_arg);
  60    // When given the same file descriptor twice, dup2() returns -1 if the
  61    // file descriptor is closed, or the given file descriptor if it is open.
  62    if (::dup2(fd, fd) == fd) {
  63      std::fprintf(stderr, "Unexpected open fd %d\n", fd);
  64      return kTextCloseOnExecHelperFoundOpenFdCode;
  65    }
  66    // Double-check that dup2() is saying the file descriptor is closed.
  67    if (errno != EBADF) {
  68      std::fprintf(stderr, "Unexpected errno after calling dup2 on fd %d: %s\n",
  69                   fd, std::strerror(errno));
  70      return kTextCloseOnExecHelperDup2FailedCode;
  71    }
  72    return 0;
  73  }
  74  
  75  // File descriptors are small non-negative integers.
  76  //
  77  // Returns void so the implementation can use ASSERT_EQ.
  78  void GetMaxFileDescriptor(int* result_fd) {
  79    // Get the maximum file descriptor number.
  80    ::rlimit fd_rlimit;
  81    ASSERT_EQ(0, ::getrlimit(RLIMIT_NOFILE, &fd_rlimit));
  82    *result_fd = fd_rlimit.rlim_cur;
  83  }
  84  
  85  // Iterates through all possible FDs and returns the currently open ones.
  86  //
  87  // Returns void so the implementation can use ASSERT_EQ.
  88  void GetOpenFileDescriptors(std::unordered_set<int>* open_fds) {
  89    int max_fd = 0;
  90    GetMaxFileDescriptor(&max_fd);
  91  
  92    for (int fd = 0; fd < max_fd; ++fd) {
  93      if (::dup2(fd, fd) != fd) {
  94        // When given the same file descriptor twice, dup2() returns -1 if the
  95        // file descriptor is closed, or the given file descriptor if it is open.
  96        //
  97        // Double-check that dup2() is saying the fd is closed.
  98        ASSERT_EQ(EBADF, errno)
  99            << "dup2() should set errno to EBADF on closed file descriptors";
 100        continue;
 101      }
 102      open_fds->insert(fd);
 103    }
 104  }
 105  
 106  // Finds an FD open since a previous call to GetOpenFileDescriptors().
 107  //
 108  // |baseline_open_fds| is the result of a previous GetOpenFileDescriptors()
 109  // call. Assumes that exactly one FD was opened since that call.
 110  //
 111  // Returns void so the implementation can use ASSERT_EQ.
 112  void GetNewlyOpenedFileDescriptor(
 113      const std::unordered_set<int>& baseline_open_fds, int* result_fd) {
 114    std::unordered_set<int> open_fds;
 115    GetOpenFileDescriptors(&open_fds);
 116    for (int fd : baseline_open_fds) {
 117      ASSERT_EQ(1, open_fds.count(fd))
 118          << "Previously opened file descriptor was closed during test setup";
 119      open_fds.erase(fd);
 120    }
 121    ASSERT_EQ(1, open_fds.size())
 122        << "Expected exactly one newly opened file descriptor during test setup";
 123    *result_fd = *open_fds.begin();
 124  }
 125  
 126  // Check that a fork()+exec()-ed child process does not have an extra open FD.
 127  void CheckCloseOnExecDoesNotLeakFDs(
 128      const std::unordered_set<int>& baseline_open_fds) {
 129    // Prepare the argument list for the child process.
 130    // execv() wants mutable buffers.
 131    char switch_buffer[sizeof(kTestCloseOnExecSwitch)];
 132    std::memcpy(switch_buffer, kTestCloseOnExecSwitch,
 133                sizeof(kTestCloseOnExecSwitch));
 134  
 135    int probed_fd;
 136    GetNewlyOpenedFileDescriptor(baseline_open_fds, &probed_fd);
 137    std::string fd_string = std::to_string(probed_fd);
 138    std::vector<char> fd_buffer(fd_string.begin(), fd_string.end());
 139    fd_buffer.emplace_back('\0');
 140  
 141    // The helper process is launched with the command below.
 142    //      env_posix_tests --test-close-on-exec-helper 3
 143    char* child_argv[] = {GetArgvZero()->data(), switch_buffer, fd_buffer.data(),
 144                          nullptr};
 145  
 146    constexpr int kForkInChildProcessReturnValue = 0;
 147    int child_pid = fork();
 148    if (child_pid == kForkInChildProcessReturnValue) {
 149      ::execv(child_argv[0], child_argv);
 150      std::fprintf(stderr, "Error spawning child process: %s\n", strerror(errno));
 151      std::exit(kTextCloseOnExecHelperExecFailedCode);
 152    }
 153  
 154    int child_status = 0;
 155    ASSERT_EQ(child_pid, ::waitpid(child_pid, &child_status, 0));
 156    ASSERT_TRUE(WIFEXITED(child_status))
 157        << "The helper process did not exit with an exit code";
 158    ASSERT_EQ(0, WEXITSTATUS(child_status))
 159        << "The helper process encountered an error";
 160  }
 161  
 162  }  // namespace
 163  
 164  #endif  // HAVE_O_CLOEXEC
 165  
 166  namespace leveldb {
 167  
 168  static const int kReadOnlyFileLimit = 4;
 169  static const int kMMapLimit = 4;
 170  
 171  class EnvPosixTest {
 172   public:
 173    static void SetFileLimits(int read_only_file_limit, int mmap_limit) {
 174      EnvPosixTestHelper::SetReadOnlyFDLimit(read_only_file_limit);
 175      EnvPosixTestHelper::SetReadOnlyMMapLimit(mmap_limit);
 176    }
 177  
 178    EnvPosixTest() : env_(Env::Default()) {}
 179  
 180    Env* env_;
 181  };
 182  
 183  TEST(EnvPosixTest, TestOpenOnRead) {
 184    // Write some test data to a single file that will be opened |n| times.
 185    std::string test_dir;
 186    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 187    std::string test_file = test_dir + "/open_on_read.txt";
 188  
 189    FILE* f = fopen(test_file.c_str(), "we");
 190    ASSERT_TRUE(f != nullptr);
 191    const char kFileData[] = "abcdefghijklmnopqrstuvwxyz";
 192    fputs(kFileData, f);
 193    fclose(f);
 194  
 195    // Open test file some number above the sum of the two limits to force
 196    // open-on-read behavior of POSIX Env leveldb::RandomAccessFile.
 197    const int kNumFiles = kReadOnlyFileLimit + kMMapLimit + 5;
 198    leveldb::RandomAccessFile* files[kNumFiles] = {0};
 199    for (int i = 0; i < kNumFiles; i++) {
 200      ASSERT_OK(env_->NewRandomAccessFile(test_file, &files[i]));
 201    }
 202    char scratch;
 203    Slice read_result;
 204    for (int i = 0; i < kNumFiles; i++) {
 205      ASSERT_OK(files[i]->Read(i, 1, &read_result, &scratch));
 206      ASSERT_EQ(kFileData[i], read_result[0]);
 207    }
 208    for (int i = 0; i < kNumFiles; i++) {
 209      delete files[i];
 210    }
 211    ASSERT_OK(env_->DeleteFile(test_file));
 212  }
 213  
 214  #if HAVE_O_CLOEXEC
 215  
 216  TEST(EnvPosixTest, TestCloseOnExecSequentialFile) {
 217    std::unordered_set<int> open_fds;
 218    GetOpenFileDescriptors(&open_fds);
 219  
 220    std::string test_dir;
 221    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 222    std::string file_path = test_dir + "/close_on_exec_sequential.txt";
 223    ASSERT_OK(WriteStringToFile(env_, "0123456789", file_path));
 224  
 225    leveldb::SequentialFile* file = nullptr;
 226    ASSERT_OK(env_->NewSequentialFile(file_path, &file));
 227    CheckCloseOnExecDoesNotLeakFDs(open_fds);
 228    delete file;
 229  
 230    ASSERT_OK(env_->DeleteFile(file_path));
 231  }
 232  
 233  TEST(EnvPosixTest, TestCloseOnExecRandomAccessFile) {
 234    std::unordered_set<int> open_fds;
 235    GetOpenFileDescriptors(&open_fds);
 236  
 237    std::string test_dir;
 238    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 239    std::string file_path = test_dir + "/close_on_exec_random_access.txt";
 240    ASSERT_OK(WriteStringToFile(env_, "0123456789", file_path));
 241  
 242    // Exhaust the RandomAccessFile mmap limit. This way, the test
 243    // RandomAccessFile instance below is backed by a file descriptor, not by an
 244    // mmap region.
 245    leveldb::RandomAccessFile* mmapped_files[kReadOnlyFileLimit] = {nullptr};
 246    for (int i = 0; i < kReadOnlyFileLimit; i++) {
 247      ASSERT_OK(env_->NewRandomAccessFile(file_path, &mmapped_files[i]));
 248    }
 249  
 250    leveldb::RandomAccessFile* file = nullptr;
 251    ASSERT_OK(env_->NewRandomAccessFile(file_path, &file));
 252    CheckCloseOnExecDoesNotLeakFDs(open_fds);
 253    delete file;
 254  
 255    for (int i = 0; i < kReadOnlyFileLimit; i++) {
 256      delete mmapped_files[i];
 257    }
 258    ASSERT_OK(env_->DeleteFile(file_path));
 259  }
 260  
 261  TEST(EnvPosixTest, TestCloseOnExecWritableFile) {
 262    std::unordered_set<int> open_fds;
 263    GetOpenFileDescriptors(&open_fds);
 264  
 265    std::string test_dir;
 266    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 267    std::string file_path = test_dir + "/close_on_exec_writable.txt";
 268    ASSERT_OK(WriteStringToFile(env_, "0123456789", file_path));
 269  
 270    leveldb::WritableFile* file = nullptr;
 271    ASSERT_OK(env_->NewWritableFile(file_path, &file));
 272    CheckCloseOnExecDoesNotLeakFDs(open_fds);
 273    delete file;
 274  
 275    ASSERT_OK(env_->DeleteFile(file_path));
 276  }
 277  
 278  TEST(EnvPosixTest, TestCloseOnExecAppendableFile) {
 279    std::unordered_set<int> open_fds;
 280    GetOpenFileDescriptors(&open_fds);
 281  
 282    std::string test_dir;
 283    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 284    std::string file_path = test_dir + "/close_on_exec_appendable.txt";
 285    ASSERT_OK(WriteStringToFile(env_, "0123456789", file_path));
 286  
 287    leveldb::WritableFile* file = nullptr;
 288    ASSERT_OK(env_->NewAppendableFile(file_path, &file));
 289    CheckCloseOnExecDoesNotLeakFDs(open_fds);
 290    delete file;
 291  
 292    ASSERT_OK(env_->DeleteFile(file_path));
 293  }
 294  
 295  TEST(EnvPosixTest, TestCloseOnExecLockFile) {
 296    std::unordered_set<int> open_fds;
 297    GetOpenFileDescriptors(&open_fds);
 298  
 299    std::string test_dir;
 300    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 301    std::string file_path = test_dir + "/close_on_exec_lock.txt";
 302    ASSERT_OK(WriteStringToFile(env_, "0123456789", file_path));
 303  
 304    leveldb::FileLock* lock = nullptr;
 305    ASSERT_OK(env_->LockFile(file_path, &lock));
 306    CheckCloseOnExecDoesNotLeakFDs(open_fds);
 307    ASSERT_OK(env_->UnlockFile(lock));
 308  
 309    ASSERT_OK(env_->DeleteFile(file_path));
 310  }
 311  
 312  TEST(EnvPosixTest, TestCloseOnExecLogger) {
 313    std::unordered_set<int> open_fds;
 314    GetOpenFileDescriptors(&open_fds);
 315  
 316    std::string test_dir;
 317    ASSERT_OK(env_->GetTestDirectory(&test_dir));
 318    std::string file_path = test_dir + "/close_on_exec_logger.txt";
 319    ASSERT_OK(WriteStringToFile(env_, "0123456789", file_path));
 320  
 321    leveldb::Logger* file = nullptr;
 322    ASSERT_OK(env_->NewLogger(file_path, &file));
 323    CheckCloseOnExecDoesNotLeakFDs(open_fds);
 324    delete file;
 325  
 326    ASSERT_OK(env_->DeleteFile(file_path));
 327  }
 328  
 329  #endif  // HAVE_O_CLOEXEC
 330  
 331  }  // namespace leveldb
 332  
 333  int main(int argc, char** argv) {
 334  #if HAVE_O_CLOEXEC
 335    // Check if we're invoked as a helper program, or as the test suite.
 336    for (int i = 1; i < argc; ++i) {
 337      if (!std::strcmp(argv[i], kTestCloseOnExecSwitch)) {
 338        return TestCloseOnExecHelperMain(argv[i + 1]);
 339      }
 340    }
 341  
 342    // Save argv[0] early, because googletest may modify argv.
 343    GetArgvZero()->assign(argv[0], argv[0] + std::strlen(argv[0]) + 1);
 344  #endif  // HAVE_O_CLOEXEC
 345  
 346    // All tests currently run with the same read-only file limits.
 347    leveldb::EnvPosixTest::SetFileLimits(leveldb::kReadOnlyFileLimit,
 348                                         leveldb::kMMapLimit);
 349    return leveldb::test::RunAllTests();
 350  }
 351