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