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