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 | |
24 | namespace { |
25 | |
26 | // Exit codes for the helper process spawned by TestCloseOnExec* tests. |
27 | // Useful for debugging test failures. |
28 | constexpr int kTextCloseOnExecHelperExecFailedCode = 61; |
29 | constexpr int kTextCloseOnExecHelperDup2FailedCode = 62; |
30 | constexpr 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. |
38 | std::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. |
44 | static 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. |
59 | int 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. |
79 | void 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. |
89 | void 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. |
113 | void 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. |
128 | void 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 | |
167 | namespace leveldb { |
168 | |
169 | static const int kReadOnlyFileLimit = 4; |
170 | static const int kMMapLimit = 4; |
171 | |
172 | class 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 | |
184 | TEST_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 | |
217 | TEST_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 | |
234 | TEST_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 | |
262 | TEST_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 | |
279 | TEST_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 | |
296 | TEST_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 | |
313 | TEST_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 | |
334 | int 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 | |