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).
5 //
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.
9 
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.
16 
17 #include "test_util/fault_injection_test_fs.h"
18 #include <functional>
19 #include <utility>
20 
21 namespace ROCKSDB_NAMESPACE {
22 
23 // Assume a filename, and not a directory name like "/foo/bar/"
TestFSGetDirName(const std::string filename)24 std::string TestFSGetDirName(const std::string filename) {
25   size_t found = filename.find_last_of("/\\");
26   if (found == std::string::npos) {
27     return "";
28   } else {
29     return filename.substr(0, found);
30   }
31 }
32 
33 // Trim the tailing "/" in the end of `str`
TestFSTrimDirname(const std::string & str)34 std::string TestFSTrimDirname(const std::string& str) {
35   size_t found = str.find_last_not_of("/");
36   if (found == std::string::npos) {
37     return str;
38   }
39   return str.substr(0, found + 1);
40 }
41 
42 // Return pair <parent directory name, file name> of a full path.
TestFSGetDirAndName(const std::string & name)43 std::pair<std::string, std::string> TestFSGetDirAndName(
44     const std::string& name) {
45   std::string dirname = TestFSGetDirName(name);
46   std::string fname = name.substr(dirname.size() + 1);
47   return std::make_pair(dirname, fname);
48 }
49 
DropUnsyncedData()50 IOStatus FSFileState::DropUnsyncedData() {
51   buffer_.resize(0);
52   return IOStatus::OK();
53 }
54 
DropRandomUnsyncedData(Random * rand)55 IOStatus FSFileState::DropRandomUnsyncedData(Random* rand) {
56   int range = static_cast<int>(buffer_.size());
57   size_t truncated_size = static_cast<size_t>(rand->Uniform(range));
58   buffer_.resize(truncated_size);
59   return IOStatus::OK();
60 }
61 
Fsync(const IOOptions & options,IODebugContext * dbg)62 IOStatus TestFSDirectory::Fsync(const IOOptions& options, IODebugContext* dbg) {
63   if (!fs_->IsFilesystemActive()) {
64     return fs_->GetError();
65   }
66   fs_->SyncDir(dirname_);
67   return dir_->Fsync(options, dbg);
68 }
69 
TestFSWritableFile(const std::string & fname,std::unique_ptr<FSWritableFile> && f,FaultInjectionTestFS * fs)70 TestFSWritableFile::TestFSWritableFile(const std::string& fname,
71                                        std::unique_ptr<FSWritableFile>&& f,
72                                        FaultInjectionTestFS* fs)
73     : state_(fname),
74       target_(std::move(f)),
75       writable_file_opened_(true),
76       fs_(fs) {
77   assert(target_ != nullptr);
78   state_.pos_ = 0;
79 }
80 
~TestFSWritableFile()81 TestFSWritableFile::~TestFSWritableFile() {
82   if (writable_file_opened_) {
83     Close(IOOptions(), nullptr);
84   }
85 }
86 
Append(const Slice & data,const IOOptions &,IODebugContext *)87 IOStatus TestFSWritableFile::Append(const Slice& data, const IOOptions&,
88                                     IODebugContext*) {
89   MutexLock l(&mutex_);
90   if (!fs_->IsFilesystemActive()) {
91     return fs_->GetError();
92   }
93   state_.buffer_.append(data.data(), data.size());
94   state_.pos_ += data.size();
95   fs_->WritableFileAppended(state_);
96   return IOStatus::OK();
97 }
98 
Close(const IOOptions & options,IODebugContext * dbg)99 IOStatus TestFSWritableFile::Close(const IOOptions& options,
100                                    IODebugContext* dbg) {
101   if (!fs_->IsFilesystemActive()) {
102     return fs_->GetError();
103   }
104   writable_file_opened_ = false;
105   IOStatus io_s;
106   io_s = target_->Append(state_.buffer_, options, dbg);
107   if (io_s.ok()) {
108     state_.buffer_.resize(0);
109     target_->Sync(options, dbg);
110     io_s = target_->Close(options, dbg);
111   }
112   if (io_s.ok()) {
113     fs_->WritableFileClosed(state_);
114   }
115   return io_s;
116 }
117 
Flush(const IOOptions &,IODebugContext *)118 IOStatus TestFSWritableFile::Flush(const IOOptions&, IODebugContext*) {
119   if (!fs_->IsFilesystemActive()) {
120     return fs_->GetError();
121   }
122   IOStatus io_s;
123   if (io_s.ok() && fs_->IsFilesystemActive()) {
124     state_.pos_at_last_flush_ = state_.pos_;
125   }
126   return io_s;
127 }
128 
Sync(const IOOptions & options,IODebugContext * dbg)129 IOStatus TestFSWritableFile::Sync(const IOOptions& options,
130                                   IODebugContext* dbg) {
131   if (!fs_->IsFilesystemActive()) {
132     return fs_->GetError();
133   }
134   IOStatus io_s = target_->Append(state_.buffer_, options, dbg);
135   state_.buffer_.resize(0);
136   target_->Sync(options, dbg);
137   state_.pos_at_last_sync_ = state_.pos_;
138   fs_->WritableFileSynced(state_);
139   return io_s;
140 }
141 
TestFSRandomRWFile(const std::string &,std::unique_ptr<FSRandomRWFile> && f,FaultInjectionTestFS * fs)142 TestFSRandomRWFile::TestFSRandomRWFile(const std::string& /*fname*/,
143                                        std::unique_ptr<FSRandomRWFile>&& f,
144                                        FaultInjectionTestFS* fs)
145     : target_(std::move(f)), file_opened_(true), fs_(fs) {
146   assert(target_ != nullptr);
147 }
148 
~TestFSRandomRWFile()149 TestFSRandomRWFile::~TestFSRandomRWFile() {
150   if (file_opened_) {
151     Close(IOOptions(), nullptr);
152   }
153 }
154 
Write(uint64_t offset,const Slice & data,const IOOptions & options,IODebugContext * dbg)155 IOStatus TestFSRandomRWFile::Write(uint64_t offset, const Slice& data,
156                                    const IOOptions& options,
157                                    IODebugContext* dbg) {
158   if (!fs_->IsFilesystemActive()) {
159     return fs_->GetError();
160   }
161   return target_->Write(offset, data, options, dbg);
162 }
163 
Read(uint64_t offset,size_t n,const IOOptions & options,Slice * result,char * scratch,IODebugContext * dbg) const164 IOStatus TestFSRandomRWFile::Read(uint64_t offset, size_t n,
165                                   const IOOptions& options, Slice* result,
166                                   char* scratch, IODebugContext* dbg) const {
167   if (!fs_->IsFilesystemActive()) {
168     return fs_->GetError();
169   }
170   return target_->Read(offset, n, options, result, scratch, dbg);
171 }
172 
Close(const IOOptions & options,IODebugContext * dbg)173 IOStatus TestFSRandomRWFile::Close(const IOOptions& options,
174                                    IODebugContext* dbg) {
175   if (!fs_->IsFilesystemActive()) {
176     return fs_->GetError();
177   }
178   file_opened_ = false;
179   return target_->Close(options, dbg);
180 }
181 
Flush(const IOOptions & options,IODebugContext * dbg)182 IOStatus TestFSRandomRWFile::Flush(const IOOptions& options,
183                                    IODebugContext* dbg) {
184   if (!fs_->IsFilesystemActive()) {
185     return fs_->GetError();
186   }
187   return target_->Flush(options, dbg);
188 }
189 
Sync(const IOOptions & options,IODebugContext * dbg)190 IOStatus TestFSRandomRWFile::Sync(const IOOptions& options,
191                                   IODebugContext* dbg) {
192   if (!fs_->IsFilesystemActive()) {
193     return fs_->GetError();
194   }
195   return target_->Sync(options, dbg);
196 }
197 
NewDirectory(const std::string & name,const IOOptions & options,std::unique_ptr<FSDirectory> * result,IODebugContext * dbg)198 IOStatus FaultInjectionTestFS::NewDirectory(
199     const std::string& name, const IOOptions& options,
200     std::unique_ptr<FSDirectory>* result, IODebugContext* dbg) {
201   std::unique_ptr<FSDirectory> r;
202   IOStatus io_s = target()->NewDirectory(name, options, &r, dbg);
203   assert(io_s.ok());
204   if (!io_s.ok()) {
205     return io_s;
206   }
207   result->reset(
208       new TestFSDirectory(this, TestFSTrimDirname(name), r.release()));
209   return IOStatus::OK();
210 }
211 
NewWritableFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSWritableFile> * result,IODebugContext * dbg)212 IOStatus FaultInjectionTestFS::NewWritableFile(
213     const std::string& fname, const FileOptions& file_opts,
214     std::unique_ptr<FSWritableFile>* result, IODebugContext* dbg) {
215   if (!IsFilesystemActive()) {
216     return GetError();
217   }
218   // Not allow overwriting files
219   IOStatus io_s = target()->FileExists(fname, IOOptions(), dbg);
220   if (io_s.ok()) {
221     return IOStatus::Corruption("File already exists.");
222   } else if (!io_s.IsNotFound()) {
223     assert(io_s.IsIOError());
224     return io_s;
225   }
226   io_s = target()->NewWritableFile(fname, file_opts, result, dbg);
227   if (io_s.ok()) {
228     result->reset(new TestFSWritableFile(fname, std::move(*result), this));
229     // WritableFileWriter* file is opened
230     // again then it will be truncated - so forget our saved state.
231     UntrackFile(fname);
232     MutexLock l(&mutex_);
233     open_files_.insert(fname);
234     auto dir_and_name = TestFSGetDirAndName(fname);
235     auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
236     list.insert(dir_and_name.second);
237   }
238   return io_s;
239 }
240 
ReopenWritableFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSWritableFile> * result,IODebugContext * dbg)241 IOStatus FaultInjectionTestFS::ReopenWritableFile(
242     const std::string& fname, const FileOptions& file_opts,
243     std::unique_ptr<FSWritableFile>* result, IODebugContext* dbg) {
244   if (!IsFilesystemActive()) {
245     return GetError();
246   }
247   IOStatus io_s = target()->ReopenWritableFile(fname, file_opts, result, dbg);
248   if (io_s.ok()) {
249     result->reset(new TestFSWritableFile(fname, std::move(*result), this));
250     // WritableFileWriter* file is opened
251     // again then it will be truncated - so forget our saved state.
252     UntrackFile(fname);
253     MutexLock l(&mutex_);
254     open_files_.insert(fname);
255     auto dir_and_name = TestFSGetDirAndName(fname);
256     auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
257     list.insert(dir_and_name.second);
258   }
259   return io_s;
260 }
261 
NewRandomRWFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSRandomRWFile> * result,IODebugContext * dbg)262 IOStatus FaultInjectionTestFS::NewRandomRWFile(
263     const std::string& fname, const FileOptions& file_opts,
264     std::unique_ptr<FSRandomRWFile>* result, IODebugContext* dbg) {
265   if (!IsFilesystemActive()) {
266     return GetError();
267   }
268   IOStatus io_s = target()->NewRandomRWFile(fname, file_opts, result, dbg);
269   if (io_s.ok()) {
270     result->reset(new TestFSRandomRWFile(fname, std::move(*result), this));
271     // WritableFileWriter* file is opened
272     // again then it will be truncated - so forget our saved state.
273     UntrackFile(fname);
274     MutexLock l(&mutex_);
275     open_files_.insert(fname);
276     auto dir_and_name = TestFSGetDirAndName(fname);
277     auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
278     list.insert(dir_and_name.second);
279   }
280   return io_s;
281 }
282 
NewRandomAccessFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSRandomAccessFile> * result,IODebugContext * dbg)283 IOStatus FaultInjectionTestFS::NewRandomAccessFile(
284     const std::string& fname, const FileOptions& file_opts,
285     std::unique_ptr<FSRandomAccessFile>* result, IODebugContext* dbg) {
286   if (!IsFilesystemActive()) {
287     return GetError();
288   }
289   return target()->NewRandomAccessFile(fname, file_opts, result, dbg);
290 }
291 
DeleteFile(const std::string & f,const IOOptions & options,IODebugContext * dbg)292 IOStatus FaultInjectionTestFS::DeleteFile(const std::string& f,
293                                           const IOOptions& options,
294                                           IODebugContext* dbg) {
295   if (!IsFilesystemActive()) {
296     return GetError();
297   }
298   IOStatus io_s = FileSystemWrapper::DeleteFile(f, options, dbg);
299   if (!io_s.ok()) {
300     fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
301             io_s.ToString().c_str());
302   }
303   if (io_s.ok()) {
304     UntrackFile(f);
305   }
306   return io_s;
307 }
308 
RenameFile(const std::string & s,const std::string & t,const IOOptions & options,IODebugContext * dbg)309 IOStatus FaultInjectionTestFS::RenameFile(const std::string& s,
310                                           const std::string& t,
311                                           const IOOptions& options,
312                                           IODebugContext* dbg) {
313   if (!IsFilesystemActive()) {
314     return GetError();
315   }
316   IOStatus io_s = FileSystemWrapper::RenameFile(s, t, options, dbg);
317 
318   if (io_s.ok()) {
319     MutexLock l(&mutex_);
320     if (db_file_state_.find(s) != db_file_state_.end()) {
321       db_file_state_[t] = db_file_state_[s];
322       db_file_state_.erase(s);
323     }
324 
325     auto sdn = TestFSGetDirAndName(s);
326     auto tdn = TestFSGetDirAndName(t);
327     if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) {
328       auto& tlist = dir_to_new_files_since_last_sync_[tdn.first];
329       assert(tlist.find(tdn.second) == tlist.end());
330       tlist.insert(tdn.second);
331     }
332   }
333 
334   return io_s;
335 }
336 
WritableFileClosed(const FSFileState & state)337 void FaultInjectionTestFS::WritableFileClosed(const FSFileState& state) {
338   MutexLock l(&mutex_);
339   if (open_files_.find(state.filename_) != open_files_.end()) {
340     db_file_state_[state.filename_] = state;
341     open_files_.erase(state.filename_);
342   }
343 }
344 
WritableFileSynced(const FSFileState & state)345 void FaultInjectionTestFS::WritableFileSynced(const FSFileState& state) {
346   MutexLock l(&mutex_);
347   if (open_files_.find(state.filename_) != open_files_.end()) {
348     if (db_file_state_.find(state.filename_) == db_file_state_.end()) {
349       db_file_state_.insert(std::make_pair(state.filename_, state));
350     } else {
351       db_file_state_[state.filename_] = state;
352     }
353   }
354 }
355 
WritableFileAppended(const FSFileState & state)356 void FaultInjectionTestFS::WritableFileAppended(const FSFileState& state) {
357   MutexLock l(&mutex_);
358   if (open_files_.find(state.filename_) != open_files_.end()) {
359     if (db_file_state_.find(state.filename_) == db_file_state_.end()) {
360       db_file_state_.insert(std::make_pair(state.filename_, state));
361     } else {
362       db_file_state_[state.filename_] = state;
363     }
364   }
365 }
366 
DropUnsyncedFileData()367 IOStatus FaultInjectionTestFS::DropUnsyncedFileData() {
368   IOStatus io_s;
369   MutexLock l(&mutex_);
370   for (std::map<std::string, FSFileState>::iterator it = db_file_state_.begin();
371        io_s.ok() && it != db_file_state_.end(); ++it) {
372     FSFileState& fs_state = it->second;
373     if (!fs_state.IsFullySynced()) {
374       io_s = fs_state.DropUnsyncedData();
375     }
376   }
377   return io_s;
378 }
379 
DropRandomUnsyncedFileData(Random * rnd)380 IOStatus FaultInjectionTestFS::DropRandomUnsyncedFileData(Random* rnd) {
381   IOStatus io_s;
382   MutexLock l(&mutex_);
383   for (std::map<std::string, FSFileState>::iterator it = db_file_state_.begin();
384        io_s.ok() && it != db_file_state_.end(); ++it) {
385     FSFileState& fs_state = it->second;
386     if (!fs_state.IsFullySynced()) {
387       io_s = fs_state.DropRandomUnsyncedData(rnd);
388     }
389   }
390   return io_s;
391 }
392 
DeleteFilesCreatedAfterLastDirSync(const IOOptions & options,IODebugContext * dbg)393 IOStatus FaultInjectionTestFS::DeleteFilesCreatedAfterLastDirSync(
394     const IOOptions& options, IODebugContext* dbg) {
395   // Because DeleteFile access this container make a copy to avoid deadlock
396   std::map<std::string, std::set<std::string>> map_copy;
397   {
398     MutexLock l(&mutex_);
399     map_copy.insert(dir_to_new_files_since_last_sync_.begin(),
400                     dir_to_new_files_since_last_sync_.end());
401   }
402 
403   for (auto& pair : map_copy) {
404     for (std::string name : pair.second) {
405       IOStatus io_s = DeleteFile(pair.first + "/" + name, options, dbg);
406       if (!io_s.ok()) {
407         return io_s;
408       }
409     }
410   }
411   return IOStatus::OK();
412 }
413 
ResetState()414 void FaultInjectionTestFS::ResetState() {
415   MutexLock l(&mutex_);
416   db_file_state_.clear();
417   dir_to_new_files_since_last_sync_.clear();
418   SetFilesystemActiveNoLock(true);
419 }
420 
UntrackFile(const std::string & f)421 void FaultInjectionTestFS::UntrackFile(const std::string& f) {
422   MutexLock l(&mutex_);
423   auto dir_and_name = TestFSGetDirAndName(f);
424   dir_to_new_files_since_last_sync_[dir_and_name.first].erase(
425       dir_and_name.second);
426   db_file_state_.erase(f);
427   open_files_.erase(f);
428 }
429 
430 }  // namespace ROCKSDB_NAMESPACE
431