diff --git a/README.md b/README.md index 2f9aff2..95d2ffb 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,14 @@ file="output1.txt" occurrence=5 parts=3 #or parts_bytes=[4096,3600,1260] persist=[1,3] + +[[injection]] +type="clear" +from="f1.txt" +timing="before" +op="fsync" +occurrence=6 +crash=true ``` I recommend following the `simple` cache configuration (indicating the cache size and using a similar configuration file as `default.toml`), since it's currently the most tested schema in our experiments. Additionally, for the section **[cache]**, you can specify the following: @@ -105,6 +113,7 @@ I recommend following the `simple` cache configuration (indicating the cache siz - **torn-seq**: This fault type is used when a sequence of system calls, targeting a single file, is executed consecutively without an intervening `fsync`. *In the example*, during the second group of consecutive writes (the group number is defined by the parameter `occurrence`), to the file "output.txt", the first and fourth writes will be persisted to disk (the writes to be persisted are defined by the parameter `persist`). After the fourth write (the last in the `persist` vector), LazyFS will crash itself. - **torn-op**: This fault type involves dividing a write system call into smaller parts, with some of these parts being persisted while others are not. In the example, the fifth write issued (the number of the write is defined by the parameter `occurrence`) to the file "output1.txt" will be divided into three equal parts if the `parts` parameter is used, or into customizable-sized parts if the `parts_bytes` parameter is defined. In the commented code, there's an example of using `parts_bytes`, where the write will be torn into three parts: the first with 4096 bytes, the second with 3600 bytes, and the last with 1200 bytes. The `persist` vector determines which parts will be persisted. After the persistence of these parts, LazyFS will crash. +- **clear-cache**: Clears unsynced data in a certain point of the execution. In the example above, this fault will be injected after (`timing`) the sixth (`occurrence`) `fsync` (`op`) to the file "f1.txt" (`from`). The `op` parameter must be a system call, and if it involves two paths (such as `rename`), the `to` parameter should also be specified. The `crash` parameter determines whether LazyFS should crash after the fault injection. Other parameters: @@ -209,6 +218,19 @@ Finally, one can control LazyFS by echoing the following commands to the configu > Kills LazyFS before executing a link operation to the file pattern 'fileabd'. +- **Kill the filesystem** after injecting `torn-op` or `torn-seq`faults: + + The parameters are the same as the ones presented in the above configuration file. Parameters that have multiple values, must be specified without the parenthesis (e.g., `persist=1,2` ). + + - ```bash + echo "lazyfs::torn-op::file=...::persist=...::parts=...::occurrence=..." > /my/path/faults.fifo + ``` + + - ```bash + echo "lazyfs::torn-seq::op=...::file=...::persist=...::occurrence=..." > /my/path/faults.fifo + ``` + + LazyFS expects that every buffer written to the FIFO file terminates with a new line character (**echo** does this by default). Thus, if using `pwrite`, for example, make sure you end the buffer with `\n`. ## Contact diff --git a/lazyfs/config/default.toml b/lazyfs/config/default.toml index e48a1cd..d36a529 100644 --- a/lazyfs/config/default.toml +++ b/lazyfs/config/default.toml @@ -12,4 +12,4 @@ blocks_per_page=1 # no_pages=10 [filesystem] log_all_operations=false -logfile="" +logfile="" \ No newline at end of file diff --git a/lazyfs/include/lazyfs/lazyfs.hpp b/lazyfs/include/lazyfs/lazyfs.hpp index 8f37918..5fcff7c 100644 --- a/lazyfs/include/lazyfs/lazyfs.hpp +++ b/lazyfs/include/lazyfs/lazyfs.hpp @@ -102,11 +102,13 @@ class LazyFS : public Fusepp::Fuse { /** * @brief Faults programmed in the configuration file. */ - unordered_map>* faults; + unordered_map>* faults; + /** * @brief Faults of LazyFS crash injected during runtime. * + * obsolete! */ std::unordered_map> crash_faults; @@ -121,16 +123,47 @@ class LazyFS : public Fusepp::Fuse { std::mutex write_lock; /** - * @brief Path of the current fault being injected. + * @brief Current faults being injected. */ - string path_injecting_fault; + vector injecting_fault; /** - * @brief Lock for path of current injected fault. + * @brief Lock for current injected faults. */ - std::mutex path_injecting_fault_lock; + std::mutex injecting_fault_lock; public: + + /** + * @brief Map of faults associated with each filesystem operation + * + */ + // operation -> [((from_rgx, to_rgx), ...] + std::unordered_map>> crash_faults_before_map; + std::unordered_map>> crash_faults_after_map; + + /** + * @brief Map of allowed operations to have a crash fault + * + */ + std::unordered_set allow_crash_fs_operations = {"unlink", + "truncate", + "fsync", + "write", + "create", + "access", + "open", + "read", + "rename", + "link", + "symlink"}; + + /** + * @brief Map of operations that have two paths + * + */ + std::unordered_set fs_op_multi_path = {"rename", "link", "symlink"}; + /** * @brief Construct a new LazyFS object. * @@ -149,7 +182,7 @@ class LazyFS : public Fusepp::Fuse { cache::config::Config* config, std::thread* faults_handler_thread, void (*fht_worker) (LazyFS* filesystem), - unordered_map>* faults); + unordered_map>* faults); /** * @brief Destroy the LazyFS object @@ -158,9 +191,9 @@ class LazyFS : public Fusepp::Fuse { ~LazyFS (); /** - * @brief Get path of the fault currently being injected. + * @brief Get faults currently being injected. */ - string get_path_injecting_fault(); + vector get_injecting_fault(); /** * @brief Fifo: (fault) Clear the cached contents @@ -182,10 +215,10 @@ class LazyFS : public Fusepp::Fuse { /** * @brief Fifo: Reports which files have unsynced data. - * @param path_to_exclude Path to be excluded from the report. + * @param paths_to_exclude Paths to be excluded from the report. * */ - void command_unsynced_data_report (string path_to_exclude); + void command_unsynced_data_report (vector paths_to_exclude); /** * @brief Checks if a programmed reorder fault for the given path and operation exists. If so, updates the counter and returns the fault. @@ -193,7 +226,7 @@ class LazyFS : public Fusepp::Fuse { * @param op Operation ('write','fsync',...) * @return Pointer to the ReorderF object */ - cache::config::ReorderF* get_and_update_reorder_fault(string path, string op); + faults::ReorderF* get_and_update_reorder_fault(string path, string op); /** * @brief Persists a write if a there is a programmed reorder fault for write in the given path and if the counter matches one of the writes to persist. @@ -328,36 +361,6 @@ class LazyFS : public Fusepp::Fuse { static int lfs_chmod (const char*, mode_t, struct fuse_file_info*); static int lfs_chown (const char*, uid_t, gid_t, fuse_file_info*); - /** - * @brief Map of faults associated with each filesystem operation - * - */ - // operation -> [((from_rgx, to_rgx), before?true:false), ...] - std::unordered_map>> crash_faults_before_map; - std::unordered_map>> crash_faults_after_map; - - /** - * @brief Map of allowed operations to have a crash fault - * - */ - std::unordered_set allow_crash_fs_operations = {"unlink", - "truncate", - "fsync", - "write", - "create", - "access", - "open", - "read", - "rename", - "link", - "symlink"}; - - /** - * @brief Map of operations that have two paths - * - */ - std::unordered_set fs_op_multi_path = {"rename", "link", "symlink"}; - /** * @brief Adds a crash fault to the faults map * @@ -373,16 +376,46 @@ class LazyFS : public Fusepp::Fuse { string crash_regex_to); /** - * @brief kills lazyfs with SIGINT if any fault condition verifies + * @brief Adds a torn-seq fault to the faults map. Returns a vector with errors if any. + * + * @param path path of the fault + * @param op system call + * @param persist which parts of the write to persist + * @return errors + */ + vector add_torn_seq_fault(string path, string op, string persist); + + /** + * @brief Adds a torn-op fault to the faults map. Returns a vector with errors if any. + * + * @param path path of the fault + * @param parts which parts of the write to persist + * @param parts_bytes division of the write in bytes + * @param persist which parts of the write to persist + * @return errors + */ + vector add_torn_op_fault(string path, string parts, string parts_bytes, string persist); + + /** + * @brief Kills lazyfs with SIGKILL if any fault condition verifies * - * @param opname operation to check - * @param optiming one of 'allow_crash_fs_operations' + * @param opname one of 'allow_crash_fs_operations' + * @param optiming timing for triggering fault operation ('before' or 'after' a given system call) * @param from_op_path source path specified in the operation * @param dest_op_path destination path specified in the operation * @param fault_type type of fault that triggered the crash */ - void - trigger_crash_fault (string opname, string optiming, string from_op_path, string to_op_path, string fault_type); + void trigger_crash_fault (string opname, string optiming, string from_op_path, string to_op_path, string fault_type); + + /** + * @brief Triggers a clear fault if condition is verified. + * + * @param opname operation name + * @param optiming timing for triggering fault operation ('before' or 'after') + * @param from_op_path source path specified in the operation + * @param dest_op_path destination path specified in the operation + */ + void trigger_configured_clear_fault (string opname, string optiming, string from_path, string to_path); }; } // namespace lazyfs diff --git a/lazyfs/src/lazyfs.cpp b/lazyfs/src/lazyfs.cpp index 9e718a5..4709ff3 100644 --- a/lazyfs/src/lazyfs.cpp +++ b/lazyfs/src/lazyfs.cpp @@ -24,6 +24,8 @@ #include #include #include +#include + // LazyFS specific imports #include @@ -70,7 +72,7 @@ LazyFS::LazyFS (Cache* cache, cache::config::Config* config, std::thread* faults_handler_thread, void (*fht_worker) (LazyFS* filesystem), - unordered_map>* faults) { + unordered_map>* faults) { this->FSConfig = config; this->FSCache = cache; @@ -78,7 +80,6 @@ LazyFS::LazyFS (Cache* cache, this->fht_worker = fht_worker; this->faults = faults; this->pending_write = NULL; - this->path_injecting_fault = "none"; for (auto const& it : this->allow_crash_fs_operations) { this->crash_faults_before_map.insert ({it, {}}); @@ -88,13 +89,9 @@ LazyFS::LazyFS (Cache* cache, LazyFS::~LazyFS () {} -string LazyFS::get_path_injecting_fault() { - string res; - path_injecting_fault_lock.lock(); - res = path_injecting_fault; - path_injecting_fault_lock.unlock(); - - return res; +vector LazyFS::get_injecting_fault() { + lock_guard guard(this->injecting_fault_lock); + return injecting_fault; } void LazyFS::trigger_crash_fault (string opname, @@ -156,14 +153,10 @@ void LazyFS::trigger_crash_fault (string opname, spdlog::critical ("Triggered fault condition (op={},timing={})", opname, optiming); - if (fault_type == CRASH) - this_ ()->command_unsynced_data_report ("none"); - else if (fault_type == TORN_OP) { - this_ ()->command_unsynced_data_report (from_op_path); - } else if (fault_type == TORN_SEQ) { - this_ ()->command_unsynced_data_report (from_op_path); - } - + this->injecting_fault_lock.lock(); + this_ ()->command_unsynced_data_report (this->injecting_fault); + this->injecting_fault_lock.unlock(); + pid_t lazyfs_pid = getpid (); spdlog::critical ("Killing LazyFS pid {}!", lazyfs_pid); @@ -174,6 +167,57 @@ void LazyFS::trigger_crash_fault (string opname, } } +void LazyFS::trigger_configured_clear_fault (string opname, + string optiming, + string from_path, + string to_path) { + + auto it = faults->find(from_path); + + if (it != faults->end()) { + auto& v_faults = it->second; + + for (auto fault : v_faults) { + faults::ClearF* clear_fault = dynamic_cast(fault); + + if (clear_fault && clear_fault->op == opname) { + + bool is_multi_path = this_ ()->fs_op_multi_path.find (opname) != this_ ()->fs_op_multi_path.end (); + + if ((is_multi_path && to_path == clear_fault->to) || !is_multi_path) { + + int current_count = clear_fault->counter.load(); + if (optiming == "before") { + current_count = clear_fault->counter.fetch_add(1); + } + + if (clear_fault->timing == optiming) { + + if (current_count == clear_fault->occurrence) { + + spdlog::critical ("Triggered fault condition (op={},timing={})", opname, optiming); + + this->injecting_fault_lock.lock(); + this_ ()->command_unsynced_data_report (this->injecting_fault); + this->injecting_fault_lock.unlock(); + + if (clear_fault->crash) { + + pid_t lazyfs_pid = getpid (); + spdlog::critical ("Killing LazyFS pid {}!", lazyfs_pid); + kill (lazyfs_pid, SIGKILL); + + } else { + this_ ()->command_fault_clear_cache (); + } + } + } + } + } + } + } +} + void LazyFS::add_crash_fault (string crash_timing, string crash_operation, string crash_regex_from, @@ -193,7 +237,106 @@ void LazyFS::add_crash_fault (string crash_timing, } } -void LazyFS::command_unsynced_data_report (string path_to_exclude) { +vector LazyFS::add_torn_op_fault(string path, string parts, string parts_bytes, string persist) { + regex number ("\\d+"); + sregex_token_iterator iter(persist.begin(), persist.end(), number); + sregex_token_iterator end; + vector persistv; + + while (iter != end) { + persistv.push_back(std::stoi(*iter)); + ++iter; + } + + vector parts_bytes_v; + if (parts_bytes != "none") { + sregex_token_iterator iter(parts_bytes.begin(), parts_bytes.end(), number); + sregex_token_iterator end; + + while (iter != end) { + parts_bytes_v.push_back(std::stoi(*iter)); + ++iter; + } + } + + int partsi = -1; + if (parts != "none") { + partsi = stoi(parts); + } + + int occurrence=1; + + faults::SplitWriteF* fault; + vector errors; + + if (partsi != -1) { + fault = new faults::SplitWriteF(occurrence, persistv, partsi); + errors = faults::SplitWriteF::validate(occurrence, persistv, partsi, std::nullopt); + } else { + fault = new faults::SplitWriteF(occurrence, persistv, parts_bytes_v); + errors = faults::SplitWriteF::validate(occurrence, persistv, std::nullopt, parts_bytes_v); + } + + bool valid_fault = true; + if (errors.size() == 0) { + + auto it = faults->find(path); + if (it == faults->end()) { + faults->insert({path, {fault}}); + } else { + //Only allows one fault per file + for (auto fault : it->second) { + if (dynamic_cast(fault) != nullptr) { + errors.push_back("Only one torn-op fault per file is allowed."); + valid_fault = false; + } + } + if (valid_fault) it->second.push_back(fault); + } + } + + if (!valid_fault) delete fault; + + return errors; +} + +vector LazyFS::add_torn_seq_fault(string path, string op, string persist) { + regex number ("\\d+"); + sregex_token_iterator iter(persist.begin(), persist.end(), number); + sregex_token_iterator end; + vector persistv; + + while (iter != end) { + persistv.push_back(std::stoi(*iter)); + ++iter; + } + + faults::ReorderF* fault = new faults::ReorderF(op, persistv, 1); + vector errors = fault->validate(); + + bool valid_fault = true; + if (errors.size() == 0) { + auto it = faults->find(path); + if (it == faults->end()) + faults->insert({path, {fault}}); + else { + for (auto fault : it->second) { + //Only allows one fault per file + if (dynamic_cast(fault) != nullptr) { + errors.push_back("Only one torn-seq fault per file is allowed."); + valid_fault = false; + } + } + if (valid_fault) it->second.push_back(fault); + } + } + + if (!valid_fault) delete fault; + + return errors; +} + +void LazyFS::command_unsynced_data_report (vector paths_to_exclude) { spdlog::warn ("[lazyfs.cmds]: report request submitted..."); @@ -214,11 +357,10 @@ void LazyFS::command_unsynced_data_report (string path_to_exclude) { auto files_mapped = FSCache->find_files_mapped_to_inode (ino); bool report = true; - if (path_to_exclude!="none") { - vector::iterator it_ino; - it_ino = std::find(files_mapped.begin(), files_mapped.end(), path_to_exclude); - if(it_ino != files_mapped.end()) report = false; + if (paths_to_exclude.size() > 0) { + report = find_first_of (files_mapped.begin(), files_mapped.end(), paths_to_exclude.begin(), paths_to_exclude.end()) != files_mapped.end(); } + if(report) { if (std::get<2> (it).size () > 0 && files_mapped.size () > 0) { @@ -269,10 +411,12 @@ void LazyFS::command_unsynced_data_report (string path_to_exclude) { *it.base ()); } } - if (path_to_exclude == "none" || path_to_exclude == "" || path_to_exclude.empty()) - spdlog::info ("[lazyfs.cmds]: report: total number of bytes un-fsynced: {} bytes.\n", - total_bytes_unsynced); + } + spdlog::info ("[lazyfs.cmds]: report: total number of bytes un-fsynced: {} bytes.\n",total_bytes_unsynced); + + if (paths_to_exclude.size() > 0) + spdlog::info ("[lazyfs.cmds]: report: info about un-fsynced bytes from some files was excluded.\n"); } } @@ -310,7 +454,7 @@ void LazyFS::restart_counter(string path, string op) { if (it != faults->end()) { auto& v_faults = it->second; for (auto fault : v_faults) { - cache::config::ReorderF* reorder_fault = dynamic_cast(fault); + faults::ReorderF* reorder_fault = dynamic_cast(fault); if (reorder_fault && reorder_fault->op == op) { reorder_fault->counter.store(0); } @@ -326,16 +470,21 @@ bool LazyFS::check_and_delete_pendingwrite(const char* path) { this->pending_write = NULL; } (this->write_lock).unlock(); + + (this->injecting_fault_lock).lock(); + (this->injecting_fault).erase(std::remove((this->injecting_fault).begin(), (this->injecting_fault).end(), path), (this->injecting_fault).end()); + (this->injecting_fault_lock).unlock(); + return res; } -cache::config::ReorderF* LazyFS::get_and_update_reorder_fault(string path, string op) { - cache::config::ReorderF* fault_r = NULL; +faults::ReorderF* LazyFS::get_and_update_reorder_fault(string path, string op) { + faults::ReorderF* fault_r = NULL; auto it = faults->find(path); if (it != faults->end()) { auto& v_faults = it->second; for (auto fault : v_faults) { - cache::config::ReorderF* reorder_fault = dynamic_cast(fault); + faults::ReorderF* reorder_fault = dynamic_cast(fault); if (reorder_fault && reorder_fault->op == op) { reorder_fault->counter.fetch_add(1); fault_r = reorder_fault; @@ -349,15 +498,15 @@ bool LazyFS::persist_write(const char* path, const char* buf, size_t size, off_t string path_str(path); int pw,fd; bool res = false; - cache::config::ReorderF* fault = get_and_update_reorder_fault(path_str,"write"); + faults::ReorderF* fault = get_and_update_reorder_fault(path_str,"write"); if (fault) { //Fault for path found (this->write_lock).lock(); if (fault->counter.load()==2) { - path_injecting_fault_lock.lock(); - path_injecting_fault = path; - path_injecting_fault_lock.unlock(); + injecting_fault_lock.lock(); + injecting_fault.push_back(path); + injecting_fault_lock.unlock(); fault->group_counter.fetch_add(1); } @@ -417,6 +566,7 @@ bool LazyFS::persist_write(const char* path, const char* buf, size_t size, off_t return res; } + bool LazyFS::split_write(const char* path, const char* buf, size_t size, off_t offset) { string path_s(path); int fd; @@ -426,12 +576,12 @@ bool LazyFS::split_write(const char* path, const char* buf, size_t size, off_t o if (it != faults->end()) { auto& v_faults = it->second; for (auto fault : v_faults) { - cache::config::SplitWriteF* split_fault = dynamic_cast(fault); + faults::SplitWriteF* split_fault = dynamic_cast(fault); if (split_fault) { - path_injecting_fault_lock.lock(); - path_injecting_fault = path; - path_injecting_fault_lock.unlock(); + injecting_fault_lock.lock(); + injecting_fault.push_back(path); + injecting_fault_lock.unlock(); split_fault->counter.fetch_add(1); @@ -660,7 +810,9 @@ int LazyFS::lfs_readdir (const char* path, int LazyFS::lfs_open (const char* path, struct fuse_file_info* fi) { - this_ ()->trigger_crash_fault ("open", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("open", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("open", "before", path, ""); + std::shared_lock lock (cache_command_lock); @@ -702,14 +854,16 @@ int LazyFS::lfs_open (const char* path, struct fuse_file_info* fi) { if (fi->flags & O_TRUNC) lfs_truncate (path, 0, fi); - this_ ()->trigger_crash_fault ("open", "after", path, "", CRASH); + this_ ()->trigger_crash_fault ("open", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("open", "after", path, ""); return 0; } int LazyFS::lfs_create (const char* path, mode_t mode, struct fuse_file_info* fi) { - this_ ()->trigger_crash_fault ("create", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("create", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("create", "before", path, ""); std::shared_lock lock (cache_command_lock); @@ -775,7 +929,8 @@ int LazyFS::lfs_create (const char* path, mode_t mode, struct fuse_file_info* fi this_ ()->FSCache->unlockItem (inode); } - this_ ()->trigger_crash_fault ("create", "after", path, "", CRASH); + this_ ()->trigger_crash_fault ("create", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("create", "after", path, ""); return 0; } @@ -786,7 +941,8 @@ int LazyFS::lfs_write (const char* path, off_t offset, struct fuse_file_info* fi) { - this_ ()->trigger_crash_fault ("write", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("write", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("write", "before", path, ""); std::shared_lock lock (cache_command_lock); @@ -1110,7 +1266,7 @@ int LazyFS::lfs_write (const char* path, // ---------------------------------------------------------------------------------- - string fault_type = CRASH; + string fault_type = CLEAR; bool crash_added = this_ () -> persist_write(path,buf,size,offset); if (!crash_added) { //At the moment only one fault type can be injected crash_added = this_ () -> split_write(path,buf,size,offset); @@ -1133,6 +1289,7 @@ int LazyFS::lfs_write (const char* path, this_ ()->trigger_crash_fault ("write", "after", path, "",fault_type); + this_ ()->trigger_configured_clear_fault ("write", "after", path, ""); return res; } @@ -1143,7 +1300,8 @@ int LazyFS::lfs_read (const char* path, off_t offset, struct fuse_file_info* fi) { - this_ ()->trigger_crash_fault ("read", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("read", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("read", "before", path, ""); std::shared_lock lock (cache_command_lock); @@ -1374,14 +1532,17 @@ int LazyFS::lfs_read (const char* path, if (fi == NULL) close (fd); - this_ ()->trigger_crash_fault ("read", "after", path, "", CRASH); + this_ ()->trigger_crash_fault ("read", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("read", "after", path, ""); return res; } int LazyFS::lfs_fsync (const char* path, int isdatasync, struct fuse_file_info* fi) { - this_ ()->trigger_crash_fault ("fsync", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("fsync", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("fsync", "before", path, ""); + std::shared_lock lock (cache_command_lock); @@ -1410,8 +1571,9 @@ int LazyFS::lfs_fsync (const char* path, int isdatasync, struct fuse_file_info* : fsync (fi->fh); - this_ ()->trigger_crash_fault ("fsync", "after", path, "", CRASH); - + this_ ()->trigger_crash_fault ("fsync", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("fsync", "after", path, ""); + return res; } @@ -1507,7 +1669,8 @@ int LazyFS::lfs_recursive_rename (const char* from, const char* to, unsigned int int LazyFS::lfs_rename (const char* from, const char* to, unsigned int flags) { - this_ ()->trigger_crash_fault ("rename", "before", from, to, CRASH); + this_ ()->trigger_crash_fault ("rename", "before", from, to, CLEAR); + this_ ()->trigger_configured_clear_fault ("rename", "before", from, to); std::shared_lock lock (cache_command_lock); @@ -1540,7 +1703,8 @@ int LazyFS::lfs_rename (const char* from, const char* to, unsigned int flags) { if (res == -1) return -errno; - this_ ()->trigger_crash_fault ("rename", "after", from, to, CRASH); + this_ ()->trigger_crash_fault ("rename", "after", from, to, CLEAR); + this_ ()->trigger_configured_clear_fault ("rename", "after", from, to); return 0; } @@ -1548,7 +1712,9 @@ int LazyFS::lfs_rename (const char* from, const char* to, unsigned int flags) { int LazyFS::lfs_truncate (const char* path, off_t truncate_size, struct fuse_file_info* fi) { cout << ">> FTRUNCATE" << endl; - this_ ()->trigger_crash_fault ("truncate", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("truncate", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("truncate", "before", path, ""); + std::shared_lock lock (cache_command_lock); @@ -1684,14 +1850,16 @@ int LazyFS::lfs_truncate (const char* path, off_t truncate_size, struct fuse_fil if (res == -1) return -errno; - this_ ()->trigger_crash_fault ("truncate", "after", path, "", CRASH); + this_ ()->trigger_crash_fault ("truncate", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("truncate", "after", path, ""); return 0; } int LazyFS::lfs_symlink (const char* from, const char* to) { - this_ ()->trigger_crash_fault ("symlink", "before", from, to, CRASH); + this_ ()->trigger_crash_fault ("symlink", "before", from, to, CLEAR); + this_ ()->trigger_configured_clear_fault ("symlink", "before", from, to); std::shared_lock lock (cache_command_lock); @@ -1705,14 +1873,15 @@ int LazyFS::lfs_symlink (const char* from, const char* to) { if (res == -1) return -errno; - this_ ()->trigger_crash_fault ("symlink", "after", from, to, CRASH); - + this_ ()->trigger_crash_fault ("symlink", "after", from, to, CLEAR); + this_ ()->trigger_configured_clear_fault ("symlink", "after", from, to); return 0; } int LazyFS::lfs_access (const char* path, int mask) { - this_ ()->trigger_crash_fault ("access", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("access", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("access", "before", path, ""); std::shared_lock lock (cache_command_lock); @@ -1726,7 +1895,8 @@ int LazyFS::lfs_access (const char* path, int mask) { if (res == -1) return -errno; - this_ ()->trigger_crash_fault ("access", "after", path, "", CRASH); + this_ ()->trigger_crash_fault ("access", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("access", "after", path, ""); return 0; } @@ -1750,7 +1920,8 @@ int LazyFS::lfs_mkdir (const char* path, mode_t mode) { int LazyFS::lfs_link (const char* from, const char* to) { - this_ ()->trigger_crash_fault ("link", "before", from, to, CRASH); + this_ ()->trigger_crash_fault ("link", "before", from, to, CLEAR); + this_ ()->trigger_configured_clear_fault ("link", "before", from, to); std::shared_lock lock (cache_command_lock); @@ -1769,7 +1940,8 @@ int LazyFS::lfs_link (const char* from, const char* to) { if (!inode.empty ()) this_ ()->FSCache->insert_inode_mapping (to, inode, true); - this_ ()->trigger_crash_fault ("link", "after", from, to, CRASH); + this_ ()->trigger_crash_fault ("link", "after", from, to, CLEAR); + this_ ()->trigger_configured_clear_fault ("link", "after", from, to); return 0; } @@ -1961,7 +2133,8 @@ off_t LazyFS::lfs_lseek (const char* path, off_t off, int whence, struct fuse_fi int LazyFS::lfs_unlink (const char* path) { - this_ ()->trigger_crash_fault ("unlink", "before", path, "", CRASH); + this_ ()->trigger_crash_fault ("unlink", "before", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("unlink", "before", path, ""); std::shared_lock lock (cache_command_lock); @@ -1984,7 +2157,8 @@ int LazyFS::lfs_unlink (const char* path) { if (res == -1) return -errno; - this_ ()->trigger_crash_fault ("unlink", "after", path, "", CRASH); + this_ ()->trigger_crash_fault ("unlink", "after", path, "", CLEAR); + this_ ()->trigger_configured_clear_fault ("unlink", "before", path, ""); return 0; } diff --git a/lazyfs/src/main.cpp b/lazyfs/src/main.cpp index df4f0ab..851f844 100644 --- a/lazyfs/src/main.cpp +++ b/lazyfs/src/main.cpp @@ -186,6 +186,181 @@ void fht_worker (LazyFS* filesystem) { fifo_lock.unlock(); } + } else if (command_str.rfind ("lazyfs::torn-op", 0) == 0) { + spdlog::info ("[lazyfs.faults.worker]: received '{}'", string (buffer)); + + std::regex rgx_global ("::"); + std::regex rgx_attrib ("="); + std::sregex_token_iterator iter_glob (command_str.begin (), + command_str.end (), + rgx_global, + -1); + std::sregex_token_iterator end; + + string file = "none"; + string parts = "none"; + string parts_bytes = "none"; + string persist = "none"; + + bool valid_fault = true; + vector errors; + + for (; iter_glob != end; ++iter_glob) { + + string current = string (*iter_glob); + + spdlog::info ("[lazyfs.faults.worker]: {}",current); + + if (current.rfind ("file=", 0) == 0) { + + string tmp_file = current.erase (0, current.find ("=") + 1); + + if (tmp_file.length () != 0) + file = tmp_file; + else { + errors.push_back ("file not specified"); + valid_fault = false; + } + + } else if (current.rfind ("parts=", 0) == 0) { + + string tmp_parts = current.erase (0, current.find ("=") + 1); + std::regex pattern(R"(\d+)"); + + if (!std::regex_match(tmp_parts, pattern)) { + errors.push_back ("parts should be a number"); + valid_fault = false; + } else + parts = tmp_parts; + + + } else if (current.rfind ("parts_bytes=", 0) == 0) { + + string tmp_parts_bytes = current.erase (0, current.find ("=") + 1); + std::regex pattern(R"((\d+,)*\d+)"); + + if (!std::regex_match(tmp_parts_bytes, pattern)) { + errors.push_back ("parts_bytes should be a list of numbers separated by commas"); + valid_fault = false; + } else + parts_bytes = tmp_parts_bytes; + + + } else if (current.rfind ("persist=", 0) == 0) { + + string tmp_persist = current.erase (0, current.find ("=") + 1); + std::regex pattern(R"((\d+,)*\d+)"); + + if (!std::regex_match(tmp_persist, pattern)) { + errors.push_back ("persist should be a list of numbers separated by commas"); + valid_fault = false; + } else + persist = tmp_persist; + + } else if (current != "lazyfs" && current != "torn-op") { + errors.push_back ("unknown attribute"); + valid_fault = false; + } + } + + if (parts=="none" && parts_bytes=="none") { + errors.push_back ("should specify 'parts' or 'parts_bytes', not both"); + valid_fault = false; + } + + vector errors_add_torn_op; + if (valid_fault) + errors_add_torn_op = filesystem->add_torn_op_fault (file, parts, parts_bytes, persist); + + if (errors_add_torn_op.size() == 0) + spdlog::info ("[lazyfs.faults.worker]: configured successfully '{}'", string (buffer)); + else { + spdlog::warn ("[lazyfs.faults.worker]: received: INVALID torn-op fault:"); + + errors.insert(errors.end(), errors_add_torn_op.begin(), errors_add_torn_op.end()); + + for (auto const err : errors) { + spdlog::warn ("[lazyfs.faults.worker]: {}", err); + } + + } + + } else if (command_str.rfind ("lazyfs::torn-seq", 0) == 0) { + spdlog::info ("[lazyfs.faults.worker]: received '{}'", string (buffer)); + + std::regex rgx_global ("::"); + std::regex rgx_attrib ("="); + std::sregex_token_iterator iter_glob (command_str.begin (), + command_str.end (), + rgx_global, + -1); + std::sregex_token_iterator end; + + string file = "none"; + string op = "none"; + string persist = "none"; + + bool valid_fault = true; + vector errors; + + for (; iter_glob != end; ++iter_glob) { + + string current = string (*iter_glob); + + if (current.rfind ("file=", 0) == 0) { + + string tmp_file = current.erase (0, current.find ("=") + 1); + + if (tmp_file.length () != 0) + file = tmp_file; + else { + errors.push_back ("bad file specification"); + valid_fault = false; + } + + } else if(current.rfind ("op=", 0) == 0) { + + string tmp_op = current.erase (0, current.find ("=") + 1); + + if (tmp_op.length () != 0) + op = tmp_op; + else { + errors.push_back ("operation not available"); + valid_fault = false; + } + } else if (current.rfind ("persist=", 0) == 0) { + + string tmp_per = current.erase (0, current.find ("=") + 1); + std::regex pattern(R"((\d+,)*\d+)"); + + if (!std::regex_match(tmp_per, pattern)) { + errors.push_back ("persist should be a list of numbers separated by commas"); + valid_fault = false; + } else + persist = tmp_per; + + } else if (current != "lazyfs" && current != "torn-seq") { + errors.push_back ("unknown attribute"); + valid_fault = false; + } + } + + vector errors_add_torn_seq; + if (valid_fault) + errors_add_torn_seq = filesystem->add_torn_seq_fault(file, op, persist); + + if (errors_add_torn_seq.size() == 0) + spdlog::info ("[lazyfs.faults.worker]: configured successfully '{}'", string (buffer)); + else { + errors.insert(errors.end(), errors_add_torn_seq.begin(), errors_add_torn_seq.end()); + + spdlog::warn ("[lazyfs.faults.worker]: received: INVALID torn-seq fault:"); + + for (auto const err : errors) { + spdlog::warn ("[lazyfs.faults.worker]: {}", err); + } + } + } else if (!strcmp (buffer, "lazyfs::display-cache-usage")) { spdlog::info ("[lazyfs.faults.worker]: received '{}'", string (buffer)); @@ -199,8 +374,8 @@ void fht_worker (LazyFS* filesystem) { } else if (!strcmp (buffer, "lazyfs::unsynced-data-report")) { spdlog::info ("[lazyfs.faults.worker]: received '{}'", string (buffer)); - string path_injecting_fault = filesystem->get_path_injecting_fault(); - filesystem->command_unsynced_data_report (path_injecting_fault); + vector injecting_fault = filesystem->get_injecting_fault (); + filesystem->command_unsynced_data_report (injecting_fault); } else if (!strcmp (buffer, "lazyfs::help")) { @@ -270,7 +445,7 @@ int main (int argc, char* argv[]) { // Load LazyFS's config - unordered_map> faults = std_config.load_config (config_path); + unordered_map> faults = std_config.load_config (config_path); // Setup logger diff --git a/libs/libpcache/CMakeLists.txt b/libs/libpcache/CMakeLists.txt index 5149341..9e4226a 100644 --- a/libs/libpcache/CMakeLists.txt +++ b/libs/libpcache/CMakeLists.txt @@ -58,6 +58,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/cache/item/data.hpp ${PROJECT_SOURCE_DIR}/include/cache/item/block_info.hpp ${PROJECT_SOURCE_DIR}/include/cache/config/config.hpp + ${PROJECT_SOURCE_DIR}/include/faults/faults.hpp ) target_sources( @@ -72,6 +73,7 @@ target_sources( src/item/data.cpp src/item/item.cpp src/config/config.cpp + src/faults.cpp ) target_link_libraries(pcache toml11::toml11 spdlog) diff --git a/libs/libpcache/include/cache/config/config.hpp b/libs/libpcache/include/cache/config/config.hpp index 5cd8339..747ad7b 100644 --- a/libs/libpcache/include/cache/config/config.hpp +++ b/libs/libpcache/include/cache/config/config.hpp @@ -9,9 +9,6 @@ #ifndef CACHE_CONFIG_HPP #define CACHE_CONFIG_HPP -#define TORN_OP "torn-op" -#define TORN_SEQ "torn-seq" -#define CRASH "crash" #include #include @@ -19,150 +16,12 @@ #include #include -using namespace std; - -namespace cache::config { - -/** - * @brief Stores a generic fault programmed in the configuration file. - */ - -class Fault { - public: - /** - * @brief Type of fault. - */ - string type; - - /** - * @brief Default constructor of a new Fault object. - */ - Fault(); - - /** - * @brief Construct a new Fault object. - * - * @param type Type of fault. - */ - Fault(string type); - - /** - * @brief Default destructor for a Fault object. - */ - virtual ~Fault(); -}; +#include -/** - * @brief Fault for splitting writes in smaller writes and reordering them (torn-op fault). -*/ -class SplitWriteF : public Fault { - public: - /** - * @brief Write occurrence. For example, of this value is set to 3, on the third write for a certain path, a fault will be injected. - */ - int occurrence; - - /** - * @brief Protected counter of writes for a certain path. - */ - std::atomic_int counter; - - /** - * @brief Which parts of the write to persist. - */ - vector persist; - /** - * @brief Number of parts to divide the write. For example, if this value is set to 3, the write will be divided into 3 same sized writes. - */ - int parts; - - /** - * @brief Specify the bytes that each part of the write will have. - */ - vector parts_bytes; - - /** - * @brief Default constructor of a new SplitWriteF object. - */ - SplitWriteF(); - - /** - * @brief Contruct a new SplitWriteF object. - */ - SplitWriteF(int occurrence, vector persist, vector parts_bytes); - - /** - * @brief Contruct a new SplitWriteF object. - * - * @param occurrence Write occurrence. - * @param persist Which parts of the write to persist. - * @param parts_bytes Division of the write in bytes. - */ - SplitWriteF(int occurrence, vector persist, int parts); - - /** - * @brief Default destructor for a SplitWriteF object. - * - * @param occurrence Write occurrence. - * @param persist Which parts of the write to persist. - * @param parts Number of same-sixed parts to divide the write. - */ - ~SplitWriteF(); -}; - -/** - * @brief Fault for reordering system calls (torn-seq fault). -*/ -class ReorderF : public Fault { - - public: - - /** - * @brief Operation related to the fault. - */ - string op; - /** - * @brief When op is called sequentially for a certain path, count the number of calls. When the sequence is broken, counter is set to 0. - */ - std::atomic_int counter; - /** - * @brief If op is a write and the vector is [3,4] it means that if op is called for a certain path sequentially, the 3th and 4th write will be persisted. - */ - vector persist; - - /** - * @brief Group of writes occurrence. For example, of this value is set to 3, on the third group of consecutive writes for a certain path, a fault will be injected. - */ - int occurrence; - - /** - * @brief Counter for the groups of writes. - */ - std::atomic_int group_counter; - - - /** - * @brief Construct a new Fault object. - * - * @param op System call (i.e. "write", ...) - * @param persist Vector with operations to persist - * @param occurrence occurrence of the group of writes to persist - */ - ReorderF(string op, vector persist, int occurrence); - - /** - * @brief Default constructor for Fault. - */ - ReorderF(); - - ~ReorderF (); -}; +using namespace std; -/** - * @brief Stores the cache configuration parameters. - * - */ +namespace cache::config { class Config { @@ -281,7 +140,7 @@ class Config { * @param filename Filename to read the config from * @return Map from files to programmed faults for those files */ - unordered_map> load_config (string filename); + unordered_map> load_config (string filename); }; } // namespace cache::config diff --git a/libs/libpcache/include/faults/faults.hpp b/libs/libpcache/include/faults/faults.hpp new file mode 100644 index 0000000..e905b28 --- /dev/null +++ b/libs/libpcache/include/faults/faults.hpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include + +#define TORN_OP "torn-op" +#define TORN_SEQ "torn-seq" +#define CLEAR "clear-cache" + +using namespace std; + +namespace faults { +/** + * @brief Stores a generic fault programmed in the configuration file. + */ + +class Fault { + public: + /** + * @brief Type of fault. + */ + string type; + + /** + * @brief Default constructor of a new Fault object. + */ + Fault(); + + /** + * @brief Construct a new Fault object. + * + * @param type Type of fault. + */ + Fault(string type); + + /** + * @brief Default destructor for a Fault object. + */ + virtual ~Fault(); +}; + +/*********************************************************************************************/ + +/** + * @brief Fault for splitting writes in smaller writes and reordering them (torn-op fault). +*/ +class SplitWriteF : public Fault { + public: + /** + * @brief Write occurrence. For example, of this value is set to 3, on the third write for a certain path, a fault will be injected. + */ + int occurrence; + + /** + * @brief Protected counter of writes for a certain path. + */ + std::atomic_int counter; + + /** + * @brief Which parts of the write to persist. + */ + vector persist; + + /** + * @brief Number of parts to divide the write. For example, if this value is set to 3, the write will be divided into 3 same sized writes. + */ + int parts; + + /** + * @brief Specify the bytes that each part of the write will have. + */ + vector parts_bytes; + + /** + * @brief Default constructor of a new SplitWriteF object. + */ + SplitWriteF(); + + /** + * @brief Contruct a new SplitWriteF object. + */ + SplitWriteF(int occurrence, vector persist, vector parts_bytes); + + /** + * @brief Contruct a new SplitWriteF object. + * + * @param occurrence Write occurrence. + * @param persist Which parts of the write to persist. + * @param parts_bytes Division of the write in bytes. + */ + SplitWriteF(int occurrence, vector persist, int parts); + + /** + * @brief Default destructor for a SplitWriteF object. + * + * @param occurrence Write occurrence. + * @param persist Which parts of the write to persist. + * @param parts Number of same-sixed parts to divide the write. + */ + ~SplitWriteF(); + + /** + * @brief Check if the parameters have correct values for the fault. + * + * @param occurrence Write occurrence. + * @param persist Which parts of the write to persist. + * @param parts Number of same-sixed parts to divide the write. + * @param parts_bytes Division of the write in bytes. + * @return Vector with errors. + */ + static vector validate(int occurrence, vector persist, optional parts, optional> parts_bytes); + +}; + +/*********************************************************************************************/ + +/** + * @brief Fault for reordering system calls (torn-seq fault). +*/ +class ReorderF : public Fault { + + public: + + /** + * @brief Operation related to the fault. + */ + string op; + /** + * @brief When op is called sequentially for a certain path, count the number of calls. When the sequence is broken, counter is set to 0. + */ + std::atomic_int counter; + /** + * @brief If op is a write and the vector is [3,4] it means that if op is called for a certain path sequentially, the 3th and 4th write will be persisted. + */ + vector persist; + + /** + * @brief Group of writes occurrence. For example, of this value is set to 3, on the third group of consecutive writes for a certain path, a fault will be injected. + */ + int occurrence; + + /** + * @brief Counter for the groups of writes. + */ + std::atomic_int group_counter; + + + /** + * @brief Construct a new Fault object. + * + * @param op System call (i.e. "write", ...) + * @param persist Vector with operations to persist + * @param occurrence occurrence of the group of writes to persist + */ + ReorderF(string op, vector persist, int occurrence); + + /** + * @brief Default constructor for Fault. + */ + ReorderF(); + + ~ReorderF (); + + /** + * @brief Check if the parameters have correct values for the fault. + * + * @param occurrence occurrence of the group of writes to persist. + * @param op System call (i.e. "write", ...). + * @param persist Vector with operations to persist. + * @return Vector with errors. + */ + vector validate(); +}; + +/*********************************************************************************************/ + +/** + * @brief Fault for clearing LazyFS's cache in a specific point of execution and, optionally, crash the process. +*/ +class ClearF : public Fault { + public: + /** + * @brief Timing of the fault ("before","after"). + */ + string timing; + + /** + * @brief System call (i.e. "write", ...). + */ + string op; + + /** + * @brief Path of the system call. + */ + string from; + + /** + * @brief Path when op requires two paths (e.g., rename system call). + */ + string to; + + /** + * @brief Occurrence of the op. + */ + int occurrence; + + /** + * @brief Counter of the op. + */ + std::atomic_int counter; + + /** + * @brief If the fault is a crash fault. + */ + bool crash; + + /** + * @brief Map of allowed operations to have a crash fault + * + */ + static const unordered_set allow_crash_fs_operations; + + /** + * @brief Map of operations that have two paths + * + */ + static const unordered_set fs_op_multi_path; + + /** + * @brief Constructor for Fault. + * + * @param timing Timing of the fault ("before","after"). + * @param op System call (i.e. "write", ...). + * @param from Path of the system call. + * @param to Path when op requires two paths (e.g., rename system call). + * @param occurrence Occurrence of the op. + * @param crash If the fault is a crash fault. + */ + ClearF(string timing, string op, string from, string to, int occurrence, bool crash); + + ~ClearF (); + + /** + * @brief Check if the parameters have correct values for the fault. + * + * @return Vector with errors. + */ + vector validate(); + +}; +// namespace faults +} \ No newline at end of file diff --git a/libs/libpcache/src/config/config.cpp b/libs/libpcache/src/config/config.cpp index 630f1c9..6466e59 100644 --- a/libs/libpcache/src/config/config.cpp +++ b/libs/libpcache/src/config/config.cpp @@ -17,66 +17,12 @@ #include #include #include +#include using namespace std; namespace cache::config { -/* Faults */ - -Fault::Fault(string type) { - this->type = type; -} - -Fault::~Fault(){} - -ReorderF::ReorderF(string op, vector persist, int occurrence) : Fault(TORN_SEQ) { - (this->counter).store(0); - this->op = op; - this->persist = persist; - this->occurrence = occurrence; - (this->group_counter).store(0); - -} - -ReorderF::ReorderF() : Fault(TORN_SEQ) { - vector v; - (this->counter).store(0); - (this->group_counter).store(0); - this->op = ""; - this->persist = v; - this->occurrence = 0; -} - -ReorderF::~ReorderF(){} - -SplitWriteF::SplitWriteF(int occurrence, vector persist, int parts) : Fault(TORN_OP) { - (this->counter).store(0); - this->occurrence = occurrence; - this->persist = persist; - this->parts = parts; -} - -SplitWriteF::SplitWriteF(int occurrence, vector persist, vector parts_bytes) : Fault(TORN_OP) { - (this->counter).store(0); - this->occurrence = occurrence; - this->persist = persist; - this->parts = -1; - this->parts_bytes = parts_bytes; -} - -SplitWriteF::SplitWriteF() : Fault(TORN_OP) { - vector v; - vector p; - (this->counter).store(0); - this->occurrence = 0; - this->persist = p; - this->parts_bytes = v; - this->parts = 0; -} - -SplitWriteF::~SplitWriteF(){} - /* Configuration */ Config::Config (size_t prealloc_bytes, int nr_blocks_per_page) { @@ -114,7 +60,7 @@ void Config::setup_config_by_size (size_t prealloc_bytes, int nr_blocks_per_page Config::~Config () {} -unordered_map> Config::load_config (string filename) { +unordered_map> Config::load_config (string filename) { const auto data = toml::parse (filename); @@ -204,7 +150,7 @@ unordered_map> Config::load_config (string filename) { } } - unordered_map> faults; //(path,[Fault1,Fault2,...]) + unordered_map> faults; //(path,[Fault1,Fault2,...]) if (data.contains ("injection")) { const auto& programmed_injections = toml::find(data,"injection"); @@ -213,102 +159,249 @@ unordered_map> Config::load_config (string filename) { if (!injection.contains("type")) throw std::runtime_error("Key 'type' for some injection of is not defined in the configuration file."); string type = toml::find(injection,"type"); + bool valid_fault = true; + string error_msg{}; + if (type == TORN_SEQ) { //Checking the values of the parameters of the reordering fault + valid_fault = true; + error_msg = "The following errors were found in the configuration file for a fault of type \"torn-seq\": \n"; + + string file{}; + if (!injection.contains("file")) { + valid_fault = false; + error_msg += "\tKey 'file' for some injection of type \"torn-seq\" is not defined in the configuration file.\n"; + } else + file = toml::find(injection,"file"); + + string op{}; + if (!injection.contains("op")) { + valid_fault = false; + error_msg += "\tKey 'op' for some injection of type \"torn-seq\" is not defined in the configuration file.\n"; + } else + op = toml::find(injection,"op"); + + int occurrence = 0; + if (!injection.contains("occurrence")) { + valid_fault = false; + error_msg += "\tKey 'occurrence' for some injection of type \"torn-seq\" is not defined in the configuration file.\n"; + } else + occurrence = toml::find(injection,"occurrence"); + + vector persist; + if (!injection.contains("persist")) { + valid_fault = false; + error_msg += "\tKey 'persist' for some injection of type \"torn-seq\" is not defined in the configuration file.\n"; + } else { + persist = toml::find>(injection,"persist"); + //Sorting the persist vector is necessary for injecting this fault + sort(persist.begin(), persist.end()); + } - if (!injection.contains("file")) throw std::runtime_error("Key 'file' for some injection of type \"torn-seq\" is not defined in the configuration file."); - string file = toml::find(injection,"file"); - - if (!injection.contains("op")) throw std::runtime_error("Key 'op' for some injection of type \"torn-seq\" is not defined in the configuration file."); - string op = toml::find(injection,"op"); - - if (!injection.contains("occurrence")) throw std::runtime_error("Key 'occurrence' for some injection of type \"torn-seq\" is not defined in the configuration file."); - int occurrence = toml::find(injection,"occurrence"); - if (occurrence <= 0) throw std::runtime_error("Key 'occurrence' for some injection of type \"torn-op\" has an invalid value in the configuration file. It should be greater than 0."); - - if (!injection.contains("persist") && op == "write") throw std::runtime_error("Key 'persist' for some injection of type \"torn-seq\" with \"write\" as op is not defined in the configuration file."); - - vector persist = toml::find>(injection,"persist"); - sort(persist.begin(), persist.end()); - - for (auto & p : persist) { - if (p <= 0) throw std::runtime_error("Key 'persist' for some injection of type \"torn-seq\" has an invalid value in the configuration file. The array must contain values greater than 0."); - } - - cache::config::ReorderF * fault = new ReorderF(op,persist,occurrence); - auto it = faults.find(file); + faults::ReorderF * fault = NULL; + vector errors; + if (valid_fault) { + fault = new faults::ReorderF(op,persist,occurrence); + vector errors = fault->validate(); + } - if (it == faults.end()) { - vector v_faults; - v_faults.push_back(fault); - faults[file] = v_faults; + if (!valid_fault || errors.size() > 0) { + for (string error : errors) { + error_msg += "\t" + error + "\n"; + } + spdlog::error(error_msg); + delete fault; + } else { - //At the moment, only one torn-seq fault per file is acceptable. - for (Fault* fault : it->second) { - ReorderF* reorder_fault = dynamic_cast(fault); - if (reorder_fault && reorder_fault->op == op) throw std::runtime_error("It is only acceptable one torn-seq fault per type of operation for a given file."); + + auto it = faults.find(file); + + if (it == faults.end()) { + vector v_faults; + v_faults.push_back(fault); + faults[file] = v_faults; + } else { + //At the moment, only one torn-seq fault per file is acceptable. + for (faults::Fault* f : it->second) { + faults::ReorderF* reorder_fault = dynamic_cast(f); + + if (reorder_fault && reorder_fault->op == op) { + valid_fault = false; + spdlog::error("It is only acceptable one torn-seq fault per type of operation for a given file."); + } + } + if (valid_fault) (it->second).push_back(fault); } - (it->second).push_back(fault); + } } else if (type == TORN_OP) { //Checking the values of the parameters of the split write fault - if (!injection.contains("file")) throw std::runtime_error("Key 'file' for some injection of type \"torn-op\" is not defined in the configuration file."); - string file = toml::find(injection,"file"); - - if (!injection.contains("occurrence")) throw std::runtime_error("Key 'occurrence' for some injection of type \"torn-op\" is not defined in the configuration file."); - int occurrence = toml::find(injection,"occurrence"); - if (occurrence <= 0) throw std::runtime_error("Key 'occurrence' for some injection of type \"torn-op\" has an invalid value in the configuration file. It should be greater than 0."); - - if (!injection.contains("persist")) throw std::runtime_error("Key 'persist' for some injection of type \"torn-op\" is not defined in the configuration file."); - vector persist = toml::find>(injection,"persist"); - - if (!injection.contains("parts") && !injection.contains("parts_bytes")) throw std::runtime_error("None of the keys 'parts' and 'key_parts' for some injection of type \"torn-op\" is defined in the configuration file. Please define at most one of them."); - - if (injection.contains("parts") && injection.contains("parts_bytes")) throw std::runtime_error("Keys 'parts' and 'key_parts' for some injection of type \"torn-op\" are exclusive in the configuration file. Please define at most one of them."); + valid_fault = true; + error_msg = "The following errors were found in the configuration file for a fault of type \"torn-op\": \n"; + + string file{}; + if (!injection.contains("file")) { + valid_fault = false; + error_msg += "\tKey 'file' for some injection of type \"torn-op\" is not defined in the configuration file.\n"; + } else + file = toml::find(injection,"file"); + + int occurrence = 0; + if (!injection.contains("occurrence")) { + valid_fault = false; + error_msg += "\tKey 'occurrence' for some injection of type \"torn-op\" is not defined in the configuration file.\n"; + } else + occurrence = toml::find(injection,"occurrence"); + + vector persist; + if (!injection.contains("persist")) { + valid_fault = false; + error_msg += "\tKey 'persist' for some injection of type \"torn-op\" is not defined in the configuration file.\n"; + } else + persist = toml::find>(injection,"persist"); + + if (!injection.contains("parts") && !injection.contains("parts_bytes")) { + valid_fault = false; + error_msg += "\tNone of the keys 'parts' and 'key_parts' for some injection of type \"torn-op\" is defined in the configuration file. Please define at most one of them.\n"; + } + + if (injection.contains("parts") && injection.contains("parts_bytes")) { + valid_fault = false; + error_msg += "\tKeys 'parts' and 'key_parts' for some injection of type \"torn-op\" are exclusive in the configuration file. Please define at most one of them.\n"; + } - cache::config::SplitWriteF * fault = NULL; + faults::SplitWriteF * fault = nullptr; + vector errors; //A split write fault either contains the parameter "parts" or the "pats_bytes" - if (injection.contains("parts")) { + if (valid_fault && injection.contains("parts")) { int parts = toml::find(injection,"parts"); - if (parts <= 0) throw std::runtime_error("Key 'parts' for some injection of type \"torn-op\" has an invalid value in the configuration file. It should be greater than 0."); + errors = faults::SplitWriteF::validate(occurrence,persist,parts,std::nullopt); - for (auto & p : persist) { - if (p <= 0 || p>parts) throw std::runtime_error("Key 'persist' for some injection of type \"torn-op\" has an invalid value in the configuration file. The array must contain values greater than 0 and lesser than the number of parts."); - } - fault = new SplitWriteF(occurrence,persist,parts); + if (errors.size() <= 0) + fault = new faults::SplitWriteF(occurrence,persist,parts); + else + valid_fault = false; } - if (injection.contains("parts_bytes")) { + if (valid_fault && injection.contains("parts_bytes")) { vector parts_bytes = toml::find>(injection,"parts_bytes"); - for (auto & part : parts_bytes) { - if (part <= 0) throw std::runtime_error("Key 'parts_bytes' for some injection of type \"torn-op\" has an invalid value in the configuration file. Every part should be greater than 0."); - } - for (auto & p : persist) { - if (p <= 0 || (size_t)p > parts_bytes.size()) throw std::runtime_error("Key 'persist' for some injection of type \"torn-op\" has an invalid value in the configuration file. The array must contain values greater than 0 and lesser than the number of parts."); + errors = faults::SplitWriteF::validate(occurrence,persist,std::nullopt,parts_bytes); + + if (errors.size() <= 0) + fault = new faults::SplitWriteF(occurrence,persist,parts_bytes); + else + valid_fault = false; + } + + if (!valid_fault) { + for (string error : errors) { + error_msg += "\t" + error + "\n"; + } + spdlog::error(error_msg); + delete fault; + + } else { + + auto it = faults.find(file); + if (it == faults.end()) { + vector v_faults; + v_faults.push_back(fault); + faults[file] = v_faults; + } else { + //At the moment, only one split write fault per file is acceptable. + for (faults::Fault* f : it->second) { + faults::SplitWriteF* splitwrite_fault = dynamic_cast(f); + if (splitwrite_fault) { + valid_fault = false; + spdlog::error("It is only acceptable one torn-op write fault per file."); + } + } + if (valid_fault) (it->second).push_back(fault); } - fault = new SplitWriteF(occurrence,persist,parts_bytes); } - auto it = faults.find(file); - if (it == faults.end()) { - vector v_faults; - v_faults.push_back(fault); - faults[file] = v_faults; + } else if (type == CLEAR) { + valid_fault = true; + error_msg = "The following errors were found in the configuration file for a fault of type \"clear-cache\": \n"; + + int occurrence = 0; + if (!injection.contains("occurrence")) { + valid_fault = false; + error_msg += "\tKey 'occurrence' for some injection of type \"clear\" is not defined in the configuration file.\n"; + } else + occurrence = toml::find(injection,"occurrence"); + + string timing{}; + if (!injection.contains("timing")) { + valid_fault = false; + error_msg += "\tKey 'timing' for some injection of type \"clear\" is not defined in the configuration file.\n"; + } else + timing = toml::find(injection,"timing"); + + string op{}; + if (!injection.contains("op")) { + valid_fault = false; + error_msg += "\tKey 'op' for some injection of type \"clear\" is not defined in the configuration file.\n"; + } else + op = toml::find(injection,"op"); + + string from = "none"; + if (injection.contains("from")) + from = toml::find(injection,"from"); + + string to = "none"; + if (injection.contains("to")) + to = toml::find(injection,"to"); + + bool crash = false; + if (!injection.contains("crash")) { + valid_fault = false; + error_msg += "\tKey 'crash' for some injection of type \"clear\" is not defined in the configuration file.\n"; + } else + crash = toml::find(injection,"crash"); + + + faults::ClearF * fault = NULL; + vector errors; + if (valid_fault) { + fault = new faults::ClearF(timing,op,from,to,occurrence,crash); + errors = fault->validate(); + } + + if (!valid_fault || errors.size() > 0) { + for (string error : errors) { + error_msg += "\t" + error + "\n"; + } + spdlog::error(error_msg); + delete fault; + } else { - //At the moment, only one split write fault per file is acceptable. - for (Fault* fault : it->second) { - SplitWriteF* splitwrite_fault = dynamic_cast(fault); - if (splitwrite_fault) throw std::runtime_error("It is only acceptable one split write fault per file."); + + auto it = faults.find(from); + if (it == faults.end()) { + vector v_faults; + v_faults.push_back(fault); + faults[from] = v_faults; + } else { + //At the moment, only one clear fault per file is acceptable. + for (faults::Fault* f : it->second) { + faults::ClearF* clear_fault = dynamic_cast(f); + if (clear_fault) { + valid_fault = false; + spdlog::error("It is only acceptable one clear fault per file."); + } + } + if (valid_fault) (it->second).push_back(fault); } - (it->second).push_back(fault); } + } else { - throw std::runtime_error("Key 'type' for some injection has an unknown value in the configuration file."); + spdlog::error("Key 'type' for some injection has an unknown value in the configuration file."); } } diff --git a/libs/libpcache/src/faults.cpp b/libs/libpcache/src/faults.cpp new file mode 100644 index 0000000..9be7980 --- /dev/null +++ b/libs/libpcache/src/faults.cpp @@ -0,0 +1,171 @@ +#include + +using namespace std; + +namespace faults { + +Fault::Fault(string type) { + this->type = type; +} + +Fault::~Fault(){} + + +// Torn seq fault +ReorderF::ReorderF(string op, vector persist, int occurrence) : Fault(TORN_SEQ) { + (this->counter).store(0); + this->op = op; + this->persist = persist; + this->occurrence = occurrence; + (this->group_counter).store(0); + +} + +ReorderF::ReorderF() : Fault(TORN_SEQ) { + vector v; + (this->counter).store(0); + (this->group_counter).store(0); + this->op = ""; + this->persist = v; + this->occurrence = 0; +} + +ReorderF::~ReorderF(){} + +vector ReorderF::validate() { + vector errors; + if (this->op != "write") { + errors.push_back("Operation must be \"write\"."); + } + if (this->occurrence <= 0) { + errors.push_back("Occurrence must be greater than 0."); + } + for (auto & p : this->persist) { + if (p <= 0) { + errors.push_back("Persist must be greater than 0."); + break; + } + } + return errors; +} + + +//Torn op fault + +SplitWriteF::SplitWriteF(int occurrence, vector persist, int parts) : Fault(TORN_OP) { + (this->counter).store(0); + this->occurrence = occurrence; + this->persist = persist; + this->parts = parts; +} + +SplitWriteF::SplitWriteF(int occurrence, vector persist, vector parts_bytes) : Fault(TORN_OP) { + (this->counter).store(0); + this->occurrence = occurrence; + this->persist = persist; + this->parts = -1; + this->parts_bytes = parts_bytes; +} + +SplitWriteF::SplitWriteF() : Fault(TORN_OP) { + vector v; + vector p; + (this->counter).store(0); + this->occurrence = 0; + this->persist = p; + this->parts_bytes = v; + this->parts = 0; +} + +SplitWriteF::~SplitWriteF() {} + +vector SplitWriteF::validate(int occurrence, vector persist, optional parts, optional> parts_bytes) { + vector errors; + if (occurrence <= 0) { + errors.push_back("Occurrence must be greater than 0."); + } + + if (parts.has_value() && parts.value() <= 0) { + errors.push_back("Parts must be greater than 0."); + } + + if (parts_bytes.has_value()) { + for (auto & p : parts_bytes.value()) { + if (p <= 0) { + errors.push_back("Parts_bytes values must be greater than 0."); + break; + } + } + } + + int nr_parts = 1; + if (parts.has_value()) nr_parts = parts.value(); + else if (parts_bytes.has_value()) nr_parts = parts_bytes.value().size(); + else errors.push_back("Parts or parts_bytes must be defined."); + + for (auto & p : persist) { + if (p <= 0 || p > nr_parts) { + errors.push_back("Persist must be greater than 0 and less than parts."); + break; + } + } + return errors; +} + +//crash fault + +const unordered_set ClearF::allow_crash_fs_operations = {"unlink", + "truncate", + "fsync", + "write", + "create", + "access", + "open", + "read", + "rename", + "link", + "symlink"}; + +const unordered_set ClearF::fs_op_multi_path = {"rename", "link", "symlink"}; + +ClearF::ClearF (string timing, string op, string from, string to, int occurrence, bool crash) : Fault(CLEAR) { + this->timing = timing; + this->op = op; + this->from = from; + this->to = to; + this->occurrence = occurrence; + this->crash = crash; + (this->counter).store(0); +} + +ClearF::~ClearF(){} + +vector ClearF::validate() { + vector errors; + if (this->occurrence <= 0) { + errors.push_back("Occurrence must be greater than 0."); + } + + if (ClearF::allow_crash_fs_operations.find(this->op) == ClearF::allow_crash_fs_operations.end()) { + errors.push_back("Operation not available."); + } + + if (this->timing != "before" && this->timing != "after") { + errors.push_back("Timing must be \"before\" or \"after\"."); + } + + if (ClearF::fs_op_multi_path.find (this->op) != ClearF::fs_op_multi_path.end ()) { + if (this->from == "none" || this->to == "none") { + errors.push_back("\"from\" and \"to\" must be set defined operations with two paths."); + } + } else { + if (this->from == "none" || this->to != "none") { + errors.push_back ("Should specify \"from\" (and not \"to\")"); + } + } + + return errors; +} + +// namespace faults +}; \ No newline at end of file