xref: /aosp_15_r20/external/leveldb/util/env_posix_test.cc (revision 9507f98c5f32dee4b5f9e4a38cd499f3ff5c4490)
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.
GetArgvZero()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.
TestCloseOnExecHelperMain(char * pid_arg)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.
GetMaxFileDescriptor(int * result_fd)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.
GetOpenFileDescriptors(std::unordered_set<int> * open_fds)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.
GetNewlyOpenedFileDescriptor(const std::unordered_set<int> & baseline_open_fds,int * result_fd)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.
CheckCloseOnExecDoesNotLeakFDs(const std::unordered_set<int> & baseline_open_fds)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:
SetFileLimits(int read_only_file_limit,int mmap_limit)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 
EnvPosixTest()179   EnvPosixTest() : env_(Env::Default()) {}
180 
181   Env* env_;
182 };
183 
TEST_F(EnvPosixTest,TestOpenOnRead)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 
TEST_F(EnvPosixTest,TestCloseOnExecSequentialFile)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 
TEST_F(EnvPosixTest,TestCloseOnExecRandomAccessFile)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[kReadOnlyFileLimit] = {nullptr};
247   for (int i = 0; i < kReadOnlyFileLimit; 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 < kReadOnlyFileLimit; i++) {
257     delete mmapped_files[i];
258   }
259   ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
260 }
261 
TEST_F(EnvPosixTest,TestCloseOnExecWritableFile)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 
TEST_F(EnvPosixTest,TestCloseOnExecAppendableFile)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 
TEST_F(EnvPosixTest,TestCloseOnExecLockFile)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 
TEST_F(EnvPosixTest,TestCloseOnExecLogger)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