/ src / leveldb / util / env_posix_test.cc
env_posix_test.cc
  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  }