1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
6 // Copyright 2014 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
10 // This test uses a custom FileSystem to keep track of the state of a file
11 // system the last "Sync". The data being written is cached in a "buffer".
12 // Only when "Sync" is called, the data will be persistent. It can similate
13 // file data loss (or entire files) not protected by a "Sync". For any of the
14 // FileSystem related operations, by specify the "IOStatus Error", a specific
15 // error can be returned when file system is not activated.
17 #include "utilities/fault_injection_fs.h"
22 #include "env/composite_env_wrapper.h"
23 #include "port/lang.h"
24 #include "port/stack_trace.h"
25 #include "util/random.h"
27 namespace ROCKSDB_NAMESPACE
{
29 // Assume a filename, and not a directory name like "/foo/bar/"
30 std::string
TestFSGetDirName(const std::string filename
) {
31 size_t found
= filename
.find_last_of("/\\");
32 if (found
== std::string::npos
) {
35 return filename
.substr(0, found
);
39 // Trim the tailing "/" in the end of `str`
40 std::string
TestFSTrimDirname(const std::string
& str
) {
41 size_t found
= str
.find_last_not_of("/");
42 if (found
== std::string::npos
) {
45 return str
.substr(0, found
+ 1);
48 // Return pair <parent directory name, file name> of a full path.
49 std::pair
<std::string
, std::string
> TestFSGetDirAndName(
50 const std::string
& name
) {
51 std::string dirname
= TestFSGetDirName(name
);
52 std::string fname
= name
.substr(dirname
.size() + 1);
53 return std::make_pair(dirname
, fname
);
56 IOStatus
FSFileState::DropUnsyncedData() {
58 return IOStatus::OK();
61 IOStatus
FSFileState::DropRandomUnsyncedData(Random
* rand
) {
62 int range
= static_cast<int>(buffer_
.size());
63 size_t truncated_size
= static_cast<size_t>(rand
->Uniform(range
));
64 buffer_
.resize(truncated_size
);
65 return IOStatus::OK();
68 IOStatus
TestFSDirectory::Fsync(const IOOptions
& options
, IODebugContext
* dbg
) {
69 if (!fs_
->IsFilesystemActive()) {
70 return fs_
->GetError();
72 fs_
->SyncDir(dirname_
);
73 return dir_
->Fsync(options
, dbg
);
76 TestFSWritableFile::TestFSWritableFile(const std::string
& fname
,
77 std::unique_ptr
<FSWritableFile
>&& f
,
78 FaultInjectionTestFS
* fs
)
80 target_(std::move(f
)),
81 writable_file_opened_(true),
83 assert(target_
!= nullptr);
87 TestFSWritableFile::~TestFSWritableFile() {
88 if (writable_file_opened_
) {
89 Close(IOOptions(), nullptr).PermitUncheckedError();
93 IOStatus
TestFSWritableFile::Append(const Slice
& data
, const IOOptions
&,
96 if (!fs_
->IsFilesystemActive()) {
97 return fs_
->GetError();
99 state_
.buffer_
.append(data
.data(), data
.size());
100 state_
.pos_
+= data
.size();
101 fs_
->WritableFileAppended(state_
);
102 return IOStatus::OK();
105 IOStatus
TestFSWritableFile::Close(const IOOptions
& options
,
106 IODebugContext
* dbg
) {
107 if (!fs_
->IsFilesystemActive()) {
108 return fs_
->GetError();
110 writable_file_opened_
= false;
112 io_s
= target_
->Append(state_
.buffer_
, options
, dbg
);
114 state_
.buffer_
.resize(0);
115 // Ignore sync errors
116 target_
->Sync(options
, dbg
).PermitUncheckedError();
117 io_s
= target_
->Close(options
, dbg
);
120 fs_
->WritableFileClosed(state_
);
125 IOStatus
TestFSWritableFile::Flush(const IOOptions
&, IODebugContext
*) {
126 if (!fs_
->IsFilesystemActive()) {
127 return fs_
->GetError();
129 if (fs_
->IsFilesystemActive()) {
130 state_
.pos_at_last_flush_
= state_
.pos_
;
132 return IOStatus::OK();
135 IOStatus
TestFSWritableFile::Sync(const IOOptions
& options
,
136 IODebugContext
* dbg
) {
137 if (!fs_
->IsFilesystemActive()) {
138 return fs_
->GetError();
140 IOStatus io_s
= target_
->Append(state_
.buffer_
, options
, dbg
);
141 state_
.buffer_
.resize(0);
142 // Ignore sync errors
143 target_
->Sync(options
, dbg
).PermitUncheckedError();
144 state_
.pos_at_last_sync_
= state_
.pos_
;
145 fs_
->WritableFileSynced(state_
);
149 TestFSRandomRWFile::TestFSRandomRWFile(const std::string
& /*fname*/,
150 std::unique_ptr
<FSRandomRWFile
>&& f
,
151 FaultInjectionTestFS
* fs
)
152 : target_(std::move(f
)), file_opened_(true), fs_(fs
) {
153 assert(target_
!= nullptr);
156 TestFSRandomRWFile::~TestFSRandomRWFile() {
158 Close(IOOptions(), nullptr).PermitUncheckedError();
162 IOStatus
TestFSRandomRWFile::Write(uint64_t offset
, const Slice
& data
,
163 const IOOptions
& options
,
164 IODebugContext
* dbg
) {
165 if (!fs_
->IsFilesystemActive()) {
166 return fs_
->GetError();
168 return target_
->Write(offset
, data
, options
, dbg
);
171 IOStatus
TestFSRandomRWFile::Read(uint64_t offset
, size_t n
,
172 const IOOptions
& options
, Slice
* result
,
173 char* scratch
, IODebugContext
* dbg
) const {
174 if (!fs_
->IsFilesystemActive()) {
175 return fs_
->GetError();
177 return target_
->Read(offset
, n
, options
, result
, scratch
, dbg
);
180 IOStatus
TestFSRandomRWFile::Close(const IOOptions
& options
,
181 IODebugContext
* dbg
) {
182 if (!fs_
->IsFilesystemActive()) {
183 return fs_
->GetError();
185 file_opened_
= false;
186 return target_
->Close(options
, dbg
);
189 IOStatus
TestFSRandomRWFile::Flush(const IOOptions
& options
,
190 IODebugContext
* dbg
) {
191 if (!fs_
->IsFilesystemActive()) {
192 return fs_
->GetError();
194 return target_
->Flush(options
, dbg
);
197 IOStatus
TestFSRandomRWFile::Sync(const IOOptions
& options
,
198 IODebugContext
* dbg
) {
199 if (!fs_
->IsFilesystemActive()) {
200 return fs_
->GetError();
202 return target_
->Sync(options
, dbg
);
205 TestFSRandomAccessFile::TestFSRandomAccessFile(const std::string
& /*fname*/,
206 std::unique_ptr
<FSRandomAccessFile
>&& f
,
207 FaultInjectionTestFS
* fs
)
208 : target_(std::move(f
)), fs_(fs
) {
209 assert(target_
!= nullptr);
212 IOStatus
TestFSRandomAccessFile::Read(uint64_t offset
, size_t n
,
213 const IOOptions
& options
, Slice
* result
,
214 char* scratch
, IODebugContext
* dbg
) const {
215 if (!fs_
->IsFilesystemActive()) {
216 return fs_
->GetError();
218 IOStatus s
= target_
->Read(offset
, n
, options
, result
, scratch
, dbg
);
220 s
= fs_
->InjectError(FaultInjectionTestFS::ErrorOperation::kRead
, result
,
221 use_direct_io(), scratch
);
226 IOStatus
FaultInjectionTestFS::NewDirectory(
227 const std::string
& name
, const IOOptions
& options
,
228 std::unique_ptr
<FSDirectory
>* result
, IODebugContext
* dbg
) {
229 std::unique_ptr
<FSDirectory
> r
;
230 IOStatus io_s
= target()->NewDirectory(name
, options
, &r
, dbg
);
235 new TestFSDirectory(this, TestFSTrimDirname(name
), r
.release()));
236 return IOStatus::OK();
239 IOStatus
FaultInjectionTestFS::NewWritableFile(
240 const std::string
& fname
, const FileOptions
& file_opts
,
241 std::unique_ptr
<FSWritableFile
>* result
, IODebugContext
* dbg
) {
242 if (!IsFilesystemActive()) {
245 if (IsFilesystemDirectWritable()) {
246 return target()->NewWritableFile(fname
, file_opts
, result
, dbg
);
249 IOStatus io_s
= target()->NewWritableFile(fname
, file_opts
, result
, dbg
);
251 result
->reset(new TestFSWritableFile(fname
, std::move(*result
), this));
252 // WritableFileWriter* file is opened
253 // again then it will be truncated - so forget our saved state.
255 MutexLock
l(&mutex_
);
256 open_files_
.insert(fname
);
257 auto dir_and_name
= TestFSGetDirAndName(fname
);
258 auto& list
= dir_to_new_files_since_last_sync_
[dir_and_name
.first
];
259 list
.insert(dir_and_name
.second
);
264 IOStatus
FaultInjectionTestFS::ReopenWritableFile(
265 const std::string
& fname
, const FileOptions
& file_opts
,
266 std::unique_ptr
<FSWritableFile
>* result
, IODebugContext
* dbg
) {
267 if (!IsFilesystemActive()) {
270 if (IsFilesystemDirectWritable()) {
271 return target()->ReopenWritableFile(fname
, file_opts
, result
, dbg
);
273 IOStatus io_s
= target()->ReopenWritableFile(fname
, file_opts
, result
, dbg
);
275 result
->reset(new TestFSWritableFile(fname
, std::move(*result
), this));
276 // WritableFileWriter* file is opened
277 // again then it will be truncated - so forget our saved state.
279 MutexLock
l(&mutex_
);
280 open_files_
.insert(fname
);
281 auto dir_and_name
= TestFSGetDirAndName(fname
);
282 auto& list
= dir_to_new_files_since_last_sync_
[dir_and_name
.first
];
283 list
.insert(dir_and_name
.second
);
288 IOStatus
FaultInjectionTestFS::NewRandomRWFile(
289 const std::string
& fname
, const FileOptions
& file_opts
,
290 std::unique_ptr
<FSRandomRWFile
>* result
, IODebugContext
* dbg
) {
291 if (!IsFilesystemActive()) {
294 if (IsFilesystemDirectWritable()) {
295 return target()->NewRandomRWFile(fname
, file_opts
, result
, dbg
);
297 IOStatus io_s
= target()->NewRandomRWFile(fname
, file_opts
, result
, dbg
);
299 result
->reset(new TestFSRandomRWFile(fname
, std::move(*result
), this));
300 // WritableFileWriter* file is opened
301 // again then it will be truncated - so forget our saved state.
303 MutexLock
l(&mutex_
);
304 open_files_
.insert(fname
);
305 auto dir_and_name
= TestFSGetDirAndName(fname
);
306 auto& list
= dir_to_new_files_since_last_sync_
[dir_and_name
.first
];
307 list
.insert(dir_and_name
.second
);
312 IOStatus
FaultInjectionTestFS::NewRandomAccessFile(
313 const std::string
& fname
, const FileOptions
& file_opts
,
314 std::unique_ptr
<FSRandomAccessFile
>* result
, IODebugContext
* dbg
) {
315 if (!IsFilesystemActive()) {
318 IOStatus io_s
= InjectError(ErrorOperation::kOpen
, nullptr, false, nullptr);
320 io_s
= target()->NewRandomAccessFile(fname
, file_opts
, result
, dbg
);
323 result
->reset(new TestFSRandomAccessFile(fname
, std::move(*result
), this));
328 IOStatus
FaultInjectionTestFS::DeleteFile(const std::string
& f
,
329 const IOOptions
& options
,
330 IODebugContext
* dbg
) {
331 if (!IsFilesystemActive()) {
334 IOStatus io_s
= FileSystemWrapper::DeleteFile(f
, options
, dbg
);
341 IOStatus
FaultInjectionTestFS::RenameFile(const std::string
& s
,
342 const std::string
& t
,
343 const IOOptions
& options
,
344 IODebugContext
* dbg
) {
345 if (!IsFilesystemActive()) {
348 IOStatus io_s
= FileSystemWrapper::RenameFile(s
, t
, options
, dbg
);
351 MutexLock
l(&mutex_
);
352 if (db_file_state_
.find(s
) != db_file_state_
.end()) {
353 db_file_state_
[t
] = db_file_state_
[s
];
354 db_file_state_
.erase(s
);
357 auto sdn
= TestFSGetDirAndName(s
);
358 auto tdn
= TestFSGetDirAndName(t
);
359 if (dir_to_new_files_since_last_sync_
[sdn
.first
].erase(sdn
.second
) != 0) {
360 auto& tlist
= dir_to_new_files_since_last_sync_
[tdn
.first
];
361 assert(tlist
.find(tdn
.second
) == tlist
.end());
362 tlist
.insert(tdn
.second
);
369 void FaultInjectionTestFS::WritableFileClosed(const FSFileState
& state
) {
370 MutexLock
l(&mutex_
);
371 if (open_files_
.find(state
.filename_
) != open_files_
.end()) {
372 db_file_state_
[state
.filename_
] = state
;
373 open_files_
.erase(state
.filename_
);
377 void FaultInjectionTestFS::WritableFileSynced(const FSFileState
& state
) {
378 MutexLock
l(&mutex_
);
379 if (open_files_
.find(state
.filename_
) != open_files_
.end()) {
380 if (db_file_state_
.find(state
.filename_
) == db_file_state_
.end()) {
381 db_file_state_
.insert(std::make_pair(state
.filename_
, state
));
383 db_file_state_
[state
.filename_
] = state
;
388 void FaultInjectionTestFS::WritableFileAppended(const FSFileState
& state
) {
389 MutexLock
l(&mutex_
);
390 if (open_files_
.find(state
.filename_
) != open_files_
.end()) {
391 if (db_file_state_
.find(state
.filename_
) == db_file_state_
.end()) {
392 db_file_state_
.insert(std::make_pair(state
.filename_
, state
));
394 db_file_state_
[state
.filename_
] = state
;
399 IOStatus
FaultInjectionTestFS::DropUnsyncedFileData() {
401 MutexLock
l(&mutex_
);
402 for (std::map
<std::string
, FSFileState
>::iterator it
= db_file_state_
.begin();
403 io_s
.ok() && it
!= db_file_state_
.end(); ++it
) {
404 FSFileState
& fs_state
= it
->second
;
405 if (!fs_state
.IsFullySynced()) {
406 io_s
= fs_state
.DropUnsyncedData();
412 IOStatus
FaultInjectionTestFS::DropRandomUnsyncedFileData(Random
* rnd
) {
414 MutexLock
l(&mutex_
);
415 for (std::map
<std::string
, FSFileState
>::iterator it
= db_file_state_
.begin();
416 io_s
.ok() && it
!= db_file_state_
.end(); ++it
) {
417 FSFileState
& fs_state
= it
->second
;
418 if (!fs_state
.IsFullySynced()) {
419 io_s
= fs_state
.DropRandomUnsyncedData(rnd
);
425 IOStatus
FaultInjectionTestFS::DeleteFilesCreatedAfterLastDirSync(
426 const IOOptions
& options
, IODebugContext
* dbg
) {
427 // Because DeleteFile access this container make a copy to avoid deadlock
428 std::map
<std::string
, std::set
<std::string
>> map_copy
;
430 MutexLock
l(&mutex_
);
431 map_copy
.insert(dir_to_new_files_since_last_sync_
.begin(),
432 dir_to_new_files_since_last_sync_
.end());
435 for (auto& pair
: map_copy
) {
436 for (std::string name
: pair
.second
) {
437 IOStatus io_s
= DeleteFile(pair
.first
+ "/" + name
, options
, dbg
);
443 return IOStatus::OK();
446 void FaultInjectionTestFS::ResetState() {
447 MutexLock
l(&mutex_
);
448 db_file_state_
.clear();
449 dir_to_new_files_since_last_sync_
.clear();
450 SetFilesystemActiveNoLock(true);
453 void FaultInjectionTestFS::UntrackFile(const std::string
& f
) {
454 MutexLock
l(&mutex_
);
455 auto dir_and_name
= TestFSGetDirAndName(f
);
456 dir_to_new_files_since_last_sync_
[dir_and_name
.first
].erase(
457 dir_and_name
.second
);
458 db_file_state_
.erase(f
);
459 open_files_
.erase(f
);
462 IOStatus
FaultInjectionTestFS::InjectError(ErrorOperation op
,
467 static_cast<ErrorContext
*>(thread_local_error_
->Get());
468 if (ctx
== nullptr || !ctx
->enable_error_injection
|| !ctx
->one_in
) {
469 return IOStatus::OK();
472 if (ctx
->rand
.OneIn(ctx
->one_in
)) {
474 if (ctx
->callstack
) {
475 free(ctx
->callstack
);
477 ctx
->callstack
= port::SaveStack(&ctx
->frames
);
483 static_cast<ErrorType
>(ctx
->rand
.Uniform(ErrorType::kErrorTypeMax
));
485 // In Direct IO mode, the actual read will read extra data due to
486 // alignment restrictions. So don't inject corruption or
487 // truncated reads as we don't know if it will actually cause a
489 ctx
->type
= ErrorType::kErrorTypeStatus
;
493 case ErrorType::kErrorTypeStatus
:
494 return IOStatus::IOError();
495 // Inject random corruption
496 case ErrorType::kErrorTypeCorruption
:
498 if (result
->data() == scratch
) {
499 uint64_t offset
= ctx
->rand
.Uniform((uint32_t)result
->size());
501 std::min
<uint64_t>(result
->size() - offset
, 64UL);
502 assert(offset
< result
->size());
503 assert(offset
+ len
<= result
->size());
505 // The randomly generated string could be identical to the
506 // original one, so retry
508 str
= ctx
->rand
.RandomString(static_cast<int>(len
));
509 } while (str
== std::string(scratch
+ offset
, len
));
510 memcpy(scratch
+ offset
, str
.data(), len
);
513 FALLTHROUGH_INTENDED
;
516 // Truncate the result
517 case ErrorType::kErrorTypeTruncated
:
519 assert(result
->size() > 0);
520 uint64_t offset
= ctx
->rand
.Uniform((uint32_t)result
->size());
521 assert(offset
< result
->size());
522 *result
= Slice(result
->data(), offset
);
531 return IOStatus::IOError();
536 return IOStatus::OK();
539 void FaultInjectionTestFS::PrintFaultBacktrace() {
540 #if defined(OS_LINUX)
542 static_cast<ErrorContext
*>(thread_local_error_
->Get());
543 if (ctx
== nullptr) {
546 fprintf(stderr
, "Injected error type = %d\n", ctx
->type
);
547 port::PrintAndFreeStack(ctx
->callstack
, ctx
->frames
);
548 ctx
->callstack
= nullptr;
552 } // namespace ROCKSDB_NAMESPACE