#pragma once // Fault-injecting adapter for the actual tracker-store filesystem calls. #include #include #include #include #include #include #include #include #include #include #include #include struct PowerCut {}; class MemoryFS; static MemoryFS* metadata_filesystem = nullptr; class File { MemoryFS* fs = nullptr; std::shared_ptr> bytes; std::string path; size_t cursor = 0; bool writable = false, directory = false, valid = false; public: File() = delete; explicit File(MemoryFS* owner) : fs(owner) {} File(MemoryFS*, std::shared_ptr>, const char*, bool, bool); operator bool() const { return valid; } bool isDirectory() const { return directory; } size_t size(); int read(uint8_t*, size_t); size_t write(const uint8_t*, size_t); void flush(); void close(); }; class MemoryFS { public: void _lockFS() {} void _unlockFS() {} MemoryFS* _getFS() { return this; } std::map>> files; std::set faults, directories; std::vector trace; size_t boundary = 0, cut_at = 0; bool short_write = false, short_read = false; std::vector replacement_on_close; unsigned write_opens = 0; void point(const std::string& event) { trace.push_back(event); if (++boundary == cut_at) throw PowerCut{}; } bool begin(const std::string& op) { point("before:" + op); return !faults.count(op); } void end(const std::string& op) { point("after:" + op); } bool exists(const char* path) { const std::string op = std::string("stat:") + path; const bool okay = begin(op); const bool present = files.count(path) || directories.count(path); end(op); return okay && present; } bool remove(const char* path) { const std::string op = std::string("remove:") + path; if (!begin(op)) return false; const bool removed = files.erase(path) + directories.erase(path) != 0; end(op); return removed; } bool rename(const char* from, const char* to) { const std::string op = std::string("rename:") + from + ":" + to; if (!begin(op) || !files.count(from) || files.count(to) || directories.count(to)) return false; files[to] = files[from]; files.erase(from); end(op); return true; } File open(const char* path, const char* mode = "r", bool create = false) { (void)create; const bool write = mode[0] == 'w'; const std::string op = std::string("open:") + (write ? "w:" : "r:") + path; if (!begin(op)) return File(this); if (write) { ++write_opens; files[path] = std::make_shared>(); if (faults.count(std::string("directory:") + path)) directories.insert(path); } if (!files.count(path) && !directories.count(path)) { end(op); return File(this); } if (!files.count(path)) files[path] = std::make_shared>(); File file(this, files[path], path, write, directories.count(path)); end(op); return file; } File open(const char* path, int mode) { assert(mode == 1); return open(path, "w"); } void put(const char* path, const std::vector& bytes) { files[path] = std::make_shared>(bytes); } std::vector get(const char* path) const { return *files.at(path); } void reset() { trace.clear(); boundary = cut_at = 0; faults.clear(); short_write = short_read = false; write_opens = 0; } }; File::File(MemoryFS* owner, std::shared_ptr> data, const char* name, bool write, bool dir) : fs(owner), bytes(data), path(name), writable(write), directory(dir), valid(true) {} size_t File::size() { const std::string op = "size:" + path; const bool okay = fs->begin(op); const size_t size = okay ? bytes->size() : 0; fs->end(op); return size; } int File::read(uint8_t* output, size_t length) { const std::string op = "read:" + path; if (!valid || directory || !fs->begin(op)) return -1; length = std::min(length, bytes->size() - cursor); if (fs->short_read && length) --length; if (length) memcpy(output, bytes->data() + cursor, length); cursor += length; fs->end(op); return static_cast(length); } size_t File::write(const uint8_t* data, size_t length) { const std::string op = "write:" + path; if (!valid || directory || !fs->begin(op)) return 0; if (fs->short_write && length) --length; bytes->insert(bytes->end(), data, data + length); fs->end(op); return length; } void File::flush() { const std::string op = "flush:" + path; if (!fs->begin(op) && !bytes->empty()) bytes->pop_back(); fs->end(op); } void File::close() { const std::string op = std::string("close:") + (writable ? "w:" : "r:") + path; if (!fs->begin(op) && writable && !bytes->empty()) (*bytes)[0] ^= 1; if (writable && !fs->replacement_on_close.empty()) *bytes = fs->replacement_on_close; valid = false; fs->end(op); } #define FILE_O_WRITE 1 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) struct lfs_info {}; static constexpr int LFS_ERR_NOENT = -2; static int lfs_stat(MemoryFS* fs, const char* path, lfs_info*) { const std::string op = std::string("stat:") + path; const bool okay = fs->begin(op); const bool present = fs->files.count(path) || fs->directories.count(path); fs->end(op); return !okay ? -5 : present ? 0 : LFS_ERR_NOENT; } #endif