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 }