1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under the BSD-style license found in the
3 // LICENSE file in the root directory of this source tree. An additional grant
4 // of patent rights can be found in the PATENTS file in the same 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 Env to keep track of the state of a filesystem as of
11 // the last "sync". It then checks for data loss errors by purposely dropping
12 // file data (or entire files) not protected by a "sync".
14 #include "util/fault_injection_test_env.h"
20 // Assume a filename, and not a directory name like "/foo/bar/"
21 std::string
GetDirName(const std::string filename
) {
22 size_t found
= filename
.find_last_of("/\\");
23 if (found
== std::string::npos
) {
26 return filename
.substr(0, found
);
30 // A basic file truncation function suitable for this test.
31 Status
Truncate(Env
* env
, const std::string
& filename
, uint64_t length
) {
32 unique_ptr
<SequentialFile
> orig_file
;
33 const EnvOptions options
;
34 Status s
= env
->NewSequentialFile(filename
, &orig_file
, options
);
36 fprintf(stderr
, "Cannot truncate file %s: %s\n", filename
.c_str(),
37 s
.ToString().c_str());
41 std::unique_ptr
<char[]> scratch(new char[length
]);
42 rocksdb::Slice result
;
43 s
= orig_file
->Read(length
, &result
, scratch
.get());
48 std::string tmp_name
= GetDirName(filename
) + "/truncate.tmp";
49 unique_ptr
<WritableFile
> tmp_file
;
50 s
= env
->NewWritableFile(tmp_name
, &tmp_file
, options
);
52 s
= tmp_file
->Append(result
);
54 s
= env
->RenameFile(tmp_name
, filename
);
56 fprintf(stderr
, "Cannot rename file %s to %s: %s\n", tmp_name
.c_str(),
57 filename
.c_str(), s
.ToString().c_str());
58 env
->DeleteFile(tmp_name
);
63 fprintf(stderr
, "Cannot truncate file %s: %s\n", filename
.c_str(),
64 s
.ToString().c_str());
70 // Trim the tailing "/" in the end of `str`
71 std::string
TrimDirname(const std::string
& str
) {
72 size_t found
= str
.find_last_not_of("/");
73 if (found
== std::string::npos
) {
76 return str
.substr(0, found
+ 1);
79 // Return pair <parent directory name, file name> of a full path.
80 std::pair
<std::string
, std::string
> GetDirAndName(const std::string
& name
) {
81 std::string dirname
= GetDirName(name
);
82 std::string fname
= name
.substr(dirname
.size() + 1);
83 return std::make_pair(dirname
, fname
);
86 Status
FileState::DropUnsyncedData(Env
* env
) const {
87 ssize_t sync_pos
= pos_at_last_sync_
== -1 ? 0 : pos_at_last_sync_
;
88 return Truncate(env
, filename_
, sync_pos
);
91 Status
FileState::DropRandomUnsyncedData(Env
* env
, Random
* rand
) const {
92 ssize_t sync_pos
= pos_at_last_sync_
== -1 ? 0 : pos_at_last_sync_
;
93 assert(pos_
>= sync_pos
);
94 int range
= static_cast<int>(pos_
- sync_pos
);
95 uint64_t truncated_size
=
96 static_cast<uint64_t>(sync_pos
) + rand
->Uniform(range
);
97 return Truncate(env
, filename_
, truncated_size
);
100 Status
TestDirectory::Fsync() {
101 env_
->SyncDir(dirname_
);
102 return dir_
->Fsync();
105 TestWritableFile::TestWritableFile(const std::string
& fname
,
106 unique_ptr
<WritableFile
>&& f
,
107 FaultInjectionTestEnv
* env
)
109 target_(std::move(f
)),
110 writable_file_opened_(true),
112 assert(target_
!= nullptr);
116 TestWritableFile::~TestWritableFile() {
117 if (writable_file_opened_
) {
122 Status
TestWritableFile::Append(const Slice
& data
) {
123 if (!env_
->IsFilesystemActive()) {
124 return Status::Corruption("Not Active");
126 Status s
= target_
->Append(data
);
128 state_
.pos_
+= data
.size();
133 Status
TestWritableFile::Close() {
134 writable_file_opened_
= false;
135 Status s
= target_
->Close();
137 env_
->WritableFileClosed(state_
);
142 Status
TestWritableFile::Flush() {
143 Status s
= target_
->Flush();
144 if (s
.ok() && env_
->IsFilesystemActive()) {
145 state_
.pos_at_last_flush_
= state_
.pos_
;
150 Status
TestWritableFile::Sync() {
151 if (!env_
->IsFilesystemActive()) {
152 return Status::IOError("FaultInjectionTestEnv: not active");
154 // No need to actual sync.
155 state_
.pos_at_last_sync_
= state_
.pos_
;
159 Status
FaultInjectionTestEnv::NewDirectory(const std::string
& name
,
160 unique_ptr
<Directory
>* result
) {
161 unique_ptr
<Directory
> r
;
162 Status s
= target()->NewDirectory(name
, &r
);
167 result
->reset(new TestDirectory(this, TrimDirname(name
), r
.release()));
171 Status
FaultInjectionTestEnv::NewWritableFile(const std::string
& fname
,
172 unique_ptr
<WritableFile
>* result
,
173 const EnvOptions
& soptions
) {
174 if (!IsFilesystemActive()) {
175 return Status::Corruption("Not Active");
177 // Not allow overwriting files
178 Status s
= target()->FileExists(fname
);
180 return Status::Corruption("File already exists.");
181 } else if (!s
.IsNotFound()) {
182 assert(s
.IsIOError());
185 s
= target()->NewWritableFile(fname
, result
, soptions
);
187 result
->reset(new TestWritableFile(fname
, std::move(*result
), this));
188 // WritableFileWriter* file is opened
189 // again then it will be truncated - so forget our saved state.
191 MutexLock
l(&mutex_
);
192 open_files_
.insert(fname
);
193 auto dir_and_name
= GetDirAndName(fname
);
194 auto& list
= dir_to_new_files_since_last_sync_
[dir_and_name
.first
];
195 list
.insert(dir_and_name
.second
);
200 Status
FaultInjectionTestEnv::DeleteFile(const std::string
& f
) {
201 if (!IsFilesystemActive()) {
202 return Status::Corruption("Not Active");
204 Status s
= EnvWrapper::DeleteFile(f
);
206 fprintf(stderr
, "Cannot delete file %s: %s\n", f
.c_str(),
207 s
.ToString().c_str());
216 Status
FaultInjectionTestEnv::RenameFile(const std::string
& s
,
217 const std::string
& t
) {
218 if (!IsFilesystemActive()) {
219 return Status::Corruption("Not Active");
221 Status ret
= EnvWrapper::RenameFile(s
, t
);
224 MutexLock
l(&mutex_
);
225 if (db_file_state_
.find(s
) != db_file_state_
.end()) {
226 db_file_state_
[t
] = db_file_state_
[s
];
227 db_file_state_
.erase(s
);
230 auto sdn
= GetDirAndName(s
);
231 auto tdn
= GetDirAndName(t
);
232 if (dir_to_new_files_since_last_sync_
[sdn
.first
].erase(sdn
.second
) != 0) {
233 auto& tlist
= dir_to_new_files_since_last_sync_
[tdn
.first
];
234 assert(tlist
.find(tdn
.second
) == tlist
.end());
235 tlist
.insert(tdn
.second
);
242 void FaultInjectionTestEnv::WritableFileClosed(const FileState
& state
) {
243 MutexLock
l(&mutex_
);
244 if (open_files_
.find(state
.filename_
) != open_files_
.end()) {
245 db_file_state_
[state
.filename_
] = state
;
246 open_files_
.erase(state
.filename_
);
250 // For every file that is not fully synced, make a call to `func` with
251 // FileState of the file as the parameter.
252 Status
FaultInjectionTestEnv::DropFileData(
253 std::function
<Status(Env
*, FileState
)> func
) {
255 MutexLock
l(&mutex_
);
256 for (std::map
<std::string
, FileState
>::const_iterator it
=
257 db_file_state_
.begin();
258 s
.ok() && it
!= db_file_state_
.end(); ++it
) {
259 const FileState
& state
= it
->second
;
260 if (!state
.IsFullySynced()) {
261 s
= func(target(), state
);
267 Status
FaultInjectionTestEnv::DropUnsyncedFileData() {
268 return DropFileData([&](Env
* env
, const FileState
& state
) {
269 return state
.DropUnsyncedData(env
);
273 Status
FaultInjectionTestEnv::DropRandomUnsyncedFileData(Random
* rnd
) {
274 return DropFileData([&](Env
* env
, const FileState
& state
) {
275 return state
.DropRandomUnsyncedData(env
, rnd
);
279 Status
FaultInjectionTestEnv::DeleteFilesCreatedAfterLastDirSync() {
280 // Because DeleteFile access this container make a copy to avoid deadlock
281 std::map
<std::string
, std::set
<std::string
>> map_copy
;
283 MutexLock
l(&mutex_
);
284 map_copy
.insert(dir_to_new_files_since_last_sync_
.begin(),
285 dir_to_new_files_since_last_sync_
.end());
288 for (auto& pair
: map_copy
) {
289 for (std::string name
: pair
.second
) {
290 Status s
= DeleteFile(pair
.first
+ "/" + name
);
298 void FaultInjectionTestEnv::ResetState() {
299 MutexLock
l(&mutex_
);
300 db_file_state_
.clear();
301 dir_to_new_files_since_last_sync_
.clear();
302 SetFilesystemActiveNoLock(true);
305 void FaultInjectionTestEnv::UntrackFile(const std::string
& f
) {
306 MutexLock
l(&mutex_
);
307 auto dir_and_name
= GetDirAndName(f
);
308 dir_to_new_files_since_last_sync_
[dir_and_name
.first
].erase(
309 dir_and_name
.second
);
310 db_file_state_
.erase(f
);
311 open_files_
.erase(f
);
313 } // namespace rocksdb