1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/command_line.h"
6 #include "base/files/file.h"
7 #include "base/files/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/test/multiprocess_test.h"
10 #include "base/test/test_timeouts.h"
11 #include "base/threading/platform_thread.h"
12 #include "base/time/time.h"
13 #include "build/build_config.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "testing/multiprocess_func_list.h"
16
17 using base::File;
18 using base::FilePath;
19
20 namespace {
21
22 // Flag for the parent to share a temp dir to the child.
23 const char kTempDirFlag[] = "temp-dir";
24
25 // Flags to control how the process locks the file.
26 const char kFileLockShared[] = "file-lock-shared";
27 const char kFileLockExclusive[] = "file-lock-exclusive";
28
29 // Flags to control how the subprocess unlocks the file.
30 const char kFileUnlock[] = "file-unlock";
31 const char kCloseUnlock[] = "close-unlock";
32 const char kExitUnlock[] = "exit-unlock";
33
34 // File to lock in temp dir.
35 const char kLockFile[] = "lockfile";
36
37 // Constants for various requests and responses, used as |signal_file| parameter
38 // to signal/wait helpers.
39 const char kSignalLockFileLocked[] = "locked.signal";
40 const char kSignalLockFileClose[] = "close.signal";
41 const char kSignalLockFileClosed[] = "closed.signal";
42 const char kSignalLockFileUnlock[] = "unlock.signal";
43 const char kSignalLockFileUnlocked[] = "unlocked.signal";
44 const char kSignalExit[] = "exit.signal";
45
46 // Signal an event by creating a file which didn't previously exist.
SignalEvent(const FilePath & signal_dir,const char * signal_file)47 bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
48 File file(signal_dir.AppendASCII(signal_file),
49 File::FLAG_CREATE | File::FLAG_WRITE);
50 return file.IsValid();
51 }
52
53 // Check whether an event was signaled.
CheckEvent(const FilePath & signal_dir,const char * signal_file)54 bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
55 File file(signal_dir.AppendASCII(signal_file),
56 File::FLAG_OPEN | File::FLAG_READ);
57 return file.IsValid();
58 }
59
60 // Busy-wait for an event to be signaled, returning false for timeout.
WaitForEventWithTimeout(const FilePath & signal_dir,const char * signal_file,const base::TimeDelta & timeout)61 bool WaitForEventWithTimeout(const FilePath& signal_dir,
62 const char* signal_file,
63 const base::TimeDelta& timeout) {
64 const base::Time finish_by = base::Time::Now() + timeout;
65 while (!CheckEvent(signal_dir, signal_file)) {
66 if (base::Time::Now() > finish_by)
67 return false;
68 base::PlatformThread::Sleep(base::Milliseconds(10));
69 }
70 return true;
71 }
72
73 // Wait forever for the event to be signaled (should never return false).
WaitForEvent(const FilePath & signal_dir,const char * signal_file)74 bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
75 return WaitForEventWithTimeout(signal_dir, signal_file,
76 base::TimeDelta::Max());
77 }
78
79 // Keep these in sync so StartChild*() can refer to correct test main.
80 #define ChildMain ChildLockUnlock
81 #define ChildMainString "ChildLockUnlock"
82
83 // Subprocess to test getting a file lock then releasing it. |kTempDirFlag|
84 // must pass in an existing temporary directory for the lockfile and signal
85 // files. One of the following flags must be passed to determine how to unlock
86 // the lock file:
87 // - |kFileUnlock| calls Unlock() to unlock.
88 // - |kCloseUnlock| calls Close() while the lock is held.
89 // - |kExitUnlock| exits while the lock is held.
MULTIPROCESS_TEST_MAIN(ChildMain)90 MULTIPROCESS_TEST_MAIN(ChildMain) {
91 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
92 const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
93 CHECK(base::DirectoryExists(temp_path));
94
95 const bool use_shared_lock = command_line->HasSwitch(kFileLockShared);
96 const bool use_exclusive_lock = command_line->HasSwitch(kFileLockExclusive);
97 CHECK_NE(use_shared_lock, use_exclusive_lock);
98
99 const File::LockMode mode =
100 use_exclusive_lock ? File::LockMode::kExclusive : File::LockMode::kShared;
101
102 // Immediately lock the file.
103 File file(temp_path.AppendASCII(kLockFile),
104 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
105 CHECK(file.IsValid());
106 CHECK_EQ(File::FILE_OK, file.Lock(mode));
107 CHECK(SignalEvent(temp_path, kSignalLockFileLocked));
108
109 if (command_line->HasSwitch(kFileUnlock)) {
110 // Wait for signal to unlock, then unlock the file.
111 CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock));
112 CHECK_EQ(File::FILE_OK, file.Unlock());
113 CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked));
114 } else if (command_line->HasSwitch(kCloseUnlock)) {
115 // Wait for the signal to close, then close the file.
116 CHECK(WaitForEvent(temp_path, kSignalLockFileClose));
117 file.Close();
118 CHECK(!file.IsValid());
119 CHECK(SignalEvent(temp_path, kSignalLockFileClosed));
120 } else {
121 CHECK(command_line->HasSwitch(kExitUnlock));
122 }
123
124 // Wait for signal to exit, so that unlock or close can be distinguished from
125 // exit.
126 CHECK(WaitForEvent(temp_path, kSignalExit));
127 return 0;
128 }
129
130 } // namespace
131
132 class FileLockingTest : public testing::Test {
133 public:
134 FileLockingTest() = default;
135 FileLockingTest(const FileLockingTest&) = delete;
136 FileLockingTest& operator=(const FileLockingTest&) = delete;
137
138 protected:
SetUp()139 void SetUp() override {
140 testing::Test::SetUp();
141
142 // Setup the temp dir and the lock file.
143 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
144 lock_file_.Initialize(
145 temp_dir_.GetPath().AppendASCII(kLockFile),
146 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
147 ASSERT_TRUE(lock_file_.IsValid());
148 }
149
SignalEvent(const char * signal_file)150 bool SignalEvent(const char* signal_file) {
151 return ::SignalEvent(temp_dir_.GetPath(), signal_file);
152 }
153
WaitForEventOrTimeout(const char * signal_file)154 bool WaitForEventOrTimeout(const char* signal_file) {
155 return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file,
156 TestTimeouts::action_timeout());
157 }
158
159 // Start a child process set to use the specified locking mode and unlock
160 // action, and wait for it to lock the file.
StartChildAndSignalLock(File::LockMode lock_mode,const char * unlock_action)161 void StartChildAndSignalLock(File::LockMode lock_mode,
162 const char* unlock_action) {
163 // Create a temporary dir and spin up a ChildLockExit subprocess against it.
164 const FilePath temp_path = temp_dir_.GetPath();
165 base::CommandLine child_command_line(
166 base::GetMultiProcessTestChildBaseCommandLine());
167 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
168 child_command_line.AppendSwitch(unlock_action);
169 switch (lock_mode) {
170 case File::LockMode::kExclusive:
171 child_command_line.AppendSwitch(kFileLockExclusive);
172 break;
173 case File::LockMode::kShared:
174 child_command_line.AppendSwitch(kFileLockShared);
175 break;
176 }
177 lock_child_ = base::SpawnMultiProcessTestChild(
178 ChildMainString, child_command_line, base::LaunchOptions());
179 ASSERT_TRUE(lock_child_.IsValid());
180
181 // Wait for the child to lock the file.
182 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked));
183 }
184
185 // Signal the child to exit cleanly.
ExitChildCleanly()186 void ExitChildCleanly() {
187 ASSERT_TRUE(SignalEvent(kSignalExit));
188 int rv = -1;
189 ASSERT_TRUE(WaitForMultiprocessTestChildExit(
190 lock_child_, TestTimeouts::action_timeout(), &rv));
191 ASSERT_EQ(0, rv);
192 }
193
194 base::ScopedTempDir temp_dir_;
195 base::File lock_file_;
196 base::Process lock_child_;
197 };
198
199 // Test that locks are released by Unlock().
TEST_F(FileLockingTest,LockAndUnlockExclusive)200 TEST_F(FileLockingTest, LockAndUnlockExclusive) {
201 StartChildAndSignalLock(File::LockMode::kExclusive, kFileUnlock);
202
203 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
204 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
205 ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
206 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
207 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
208 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
209 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
210 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
211
212 ExitChildCleanly();
213 }
TEST_F(FileLockingTest,LockAndUnlockShared)214 TEST_F(FileLockingTest, LockAndUnlockShared) {
215 StartChildAndSignalLock(File::LockMode::kShared, kFileUnlock);
216
217 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
218 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
219 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
220 ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
221 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
222 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
223 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
224 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
225 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
226
227 ExitChildCleanly();
228 }
229
230 // Test that locks are released on Close().
TEST_F(FileLockingTest,UnlockOnCloseExclusive)231 TEST_F(FileLockingTest, UnlockOnCloseExclusive) {
232 StartChildAndSignalLock(File::LockMode::kExclusive, kCloseUnlock);
233
234 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
235 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
236 ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
237 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
238 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
239 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
240 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
241 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
242
243 ExitChildCleanly();
244 }
TEST_F(FileLockingTest,UnlockOnCloseShared)245 TEST_F(FileLockingTest, UnlockOnCloseShared) {
246 StartChildAndSignalLock(File::LockMode::kShared, kCloseUnlock);
247
248 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
249 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
250 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
251 ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
252 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
253 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
254 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
255 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
256 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
257
258 ExitChildCleanly();
259 }
260
261 // Test that locks are released on exit.
TEST_F(FileLockingTest,UnlockOnExitExclusive)262 TEST_F(FileLockingTest, UnlockOnExitExclusive) {
263 StartChildAndSignalLock(File::LockMode::kExclusive, kExitUnlock);
264
265 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
266 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
267 ExitChildCleanly();
268 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
269 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
270 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
271 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
272 }
TEST_F(FileLockingTest,UnlockOnExitShared)273 TEST_F(FileLockingTest, UnlockOnExitShared) {
274 StartChildAndSignalLock(File::LockMode::kShared, kExitUnlock);
275
276 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
277 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
278 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
279 ExitChildCleanly();
280 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
281 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
282 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
283 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
284 }
285
286 // Test that killing the process releases the lock. This should cover crashing.
287 // Flaky on Android (http://crbug.com/747518)
288 #if BUILDFLAG(IS_ANDROID)
289 #define MAYBE_UnlockOnTerminate DISABLED_UnlockOnTerminate
290 #else
291 #define MAYBE_UnlockOnTerminate UnlockOnTerminate
292 #endif
TEST_F(FileLockingTest,MAYBE_UnlockOnTerminate)293 TEST_F(FileLockingTest, MAYBE_UnlockOnTerminate) {
294 // The child will wait for an exit which never arrives.
295 StartChildAndSignalLock(File::LockMode::kExclusive, kExitUnlock);
296
297 ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
298 ASSERT_TRUE(TerminateMultiProcessTestChild(lock_child_, 0, true));
299 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared));
300 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
301 ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive));
302 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
303 }
304