| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fusebox/file_system_fake.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/logging.h> |
| #include <base/no_destructor.h> |
| #include <base/strings/string_piece.h> |
| |
| #include "fusebox/fuse_file_handles.h" |
| #include "fusebox/fuse_path_inodes.h" |
| #include "fusebox/fuse_request.h" |
| #include "fusebox/make_stat.h" |
| #include "fusebox/util.h" |
| |
| namespace { |
| |
| void ShowStat(const struct stat& stat, const std::string& name = {}) { |
| const char* type = S_ISDIR(stat.st_mode) ? "DIR" : "REG"; |
| |
| LOG(INFO) << " ENTRY: " << name; |
| LOG(INFO) << " ls: " << fusebox::StatModeToString(stat.st_mode); |
| LOG(INFO) << " mode: " << type << std::hex << " " << stat.st_mode; |
| LOG(INFO) << " dev: " << stat.st_dev; |
| LOG(INFO) << " ino: " << stat.st_ino; |
| LOG(INFO) << " size: " << stat.st_size; |
| LOG(INFO) << " nlink: " << stat.st_nlink; |
| LOG(INFO) << " rdev: " << stat.st_rdev; |
| LOG(INFO) << " uid: " << stat.st_uid; |
| LOG(INFO) << " gid: " << stat.st_gid; |
| LOG(INFO) << " atime: " << stat.st_atime; |
| LOG(INFO) << " mtime: " << stat.st_mtime; |
| LOG(INFO) << " ctime: " << stat.st_ctime; |
| } |
| |
| } // namespace |
| |
| namespace fusebox { |
| |
| static auto& GetInodeTable() { |
| static base::NoDestructor<InodeTable> inode_table; |
| return *inode_table; |
| } |
| |
| static auto& GetDirEntryVector() { |
| static base::NoDestructor<std::vector<struct DirEntry>> entry_vector; |
| return *entry_vector; |
| } |
| |
| FileSystemFake::FileSystemFake() = default; |
| |
| FileSystemFake::~FileSystemFake() = default; |
| |
| void FileSystemFake::Init(void* userdata, struct fuse_conn_info*) { |
| LOG(INFO) << "Init"; |
| CHECK(userdata); |
| |
| const auto time_now = std::time(nullptr); |
| const bool read_only = true; |
| struct stat parent; // parent node: assume `pwd`. |
| CHECK_EQ(0, stat(".", &parent)); |
| |
| struct stat stat = {0}; |
| stat.st_atime = time_now; |
| stat.st_mtime = time_now; |
| stat.st_ctime = time_now; |
| |
| Node* root = GetInodeTable().Lookup(1); |
| stat.st_mode = S_IFDIR | 0777; |
| auto root_stat = MakeStat(root->ino, stat, read_only); |
| GetInodeTable().SetStat(root->ino, root_stat); |
| ShowStat(root_stat, root->name); |
| |
| Node* hello = GetInodeTable().Create(1, "hello"); |
| stat.st_mode = S_IFREG | 0777; |
| stat.st_size = strlen("hello\r\n"); |
| auto hello_stat = MakeStat(hello->ino, stat, read_only); |
| GetInodeTable().SetStat(hello->ino, hello_stat); |
| ShowStat(hello_stat, hello->name); |
| |
| std::vector<struct DirEntry>& entry = GetDirEntryVector(); |
| entry.push_back({root->ino, ".", root_stat.st_mode}); |
| entry.push_back({parent.st_ino, "..", parent.st_mode}); |
| entry.push_back({hello->ino, "hello", hello_stat.st_mode}); |
| } |
| |
| void FileSystemFake::Lookup(std::unique_ptr<EntryRequest> request, |
| fuse_ino_t parent, |
| const char* name) { |
| LOG(INFO) << "Lookup parent " << parent << " name " << name; |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| Node* node = GetInodeTable().Lookup(parent, name); |
| if (!node) { |
| PLOG(ERROR) << " lookup error"; |
| request->ReplyError(errno); |
| return; |
| } |
| |
| struct stat stat; |
| CHECK(GetInodeTable().GetStat(node->ino, &stat)); |
| CHECK_EQ(stat.st_ino, node->ino); |
| |
| const double kEntryTimeoutSeconds = 5.0; |
| fuse_entry_param entry = {0}; |
| entry.ino = node->ino; |
| entry.attr = stat; |
| entry.attr_timeout = kEntryTimeoutSeconds; |
| entry.entry_timeout = kEntryTimeoutSeconds; |
| |
| LOG(INFO) << " found ino " << node->ino; |
| request->ReplyEntry(entry); |
| } |
| |
| void FileSystemFake::GetAttr(std::unique_ptr<AttrRequest> request, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "GetAttr ino " << ino; |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| Node* node = GetInodeTable().Lookup(ino); |
| if (!node) { |
| PLOG(ERROR) << " getattr error"; |
| request->ReplyError(errno); |
| return; |
| } |
| |
| struct stat stat; |
| CHECK(GetInodeTable().GetStat(node->ino, &stat)); |
| CHECK_EQ(stat.st_ino, node->ino); |
| |
| const double kStatTimeoutSeconds = 5.0; |
| request->ReplyAttr(stat, kStatTimeoutSeconds); |
| } |
| |
| void FileSystemFake::OpenDir(std::unique_ptr<OpenRequest> request, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "OpenDir ino " << ino; |
| CHECK(fi); |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| Node* node = GetInodeTable().Lookup(ino); |
| if (!node) { |
| PLOG(ERROR) << " opendir error"; |
| request->ReplyError(errno); |
| return; |
| } |
| |
| struct stat stat; |
| CHECK(GetInodeTable().GetStat(node->ino, &stat)); |
| CHECK_EQ(stat.st_ino, node->ino); |
| |
| if (!S_ISDIR(stat.st_mode)) { |
| LOG(ERROR) << " opendir error: ENOTDIR"; |
| request->ReplyError(ENOTDIR); |
| return; |
| } |
| |
| LOG(INFO) << " " << OpenFlagsToString(fi->flags); |
| if ((fi->flags & O_ACCMODE) != O_RDONLY) { |
| LOG(ERROR) << " opendir error: EACCES"; |
| request->ReplyError(EACCES); |
| return; |
| } |
| |
| uint64_t handle = fusebox::OpenFile(); |
| LOG(INFO) << " opendir fh " << handle; |
| request->ReplyOpen(handle); |
| } |
| |
| void FileSystemFake::ReadDir(std::unique_ptr<DirEntryRequest> request, |
| fuse_ino_t ino, |
| off_t off, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "ReadDir ino " << ino << " off " << off; |
| CHECK(fi); |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| if (!fusebox::GetFile(fi->fh)) { |
| LOG(ERROR) << " readdir error: EBADF"; |
| request->ReplyError(EBADF); |
| return; |
| } |
| |
| if (off != 0) { |
| LOG(INFO) << " readdir done fh " << fi->fh; |
| request->ReplyDone(); |
| return; |
| } |
| |
| off_t next = 0; |
| LOG(INFO) << " readdir fh " << fi->fh; |
| for (const auto& entry : GetDirEntryVector()) { |
| LOG(INFO) << " entry [" << entry.name << "]"; |
| CHECK(request->AddEntry(entry, ++next)); |
| } |
| |
| request->ReplyDone(); |
| } |
| |
| void FileSystemFake::ReleaseDir(std::unique_ptr<OkRequest> request, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "ReleaseDir ino " << ino; |
| CHECK(fi); |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| if (!fusebox::GetFile(fi->fh)) { |
| LOG(ERROR) << " releasedir error: EBADF"; |
| request->ReplyError(EBADF); |
| return; |
| } |
| |
| LOG(INFO) << " releasedir fh " << fi->fh; |
| fusebox::CloseFile(fi->fh); |
| request->ReplyOk(); |
| } |
| |
| void FileSystemFake::Open(std::unique_ptr<OpenRequest> request, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "Open ino " << ino; |
| CHECK(fi); |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| Node* node = GetInodeTable().Lookup(ino); |
| if (!node) { |
| PLOG(ERROR) << " open error"; |
| request->ReplyError(errno); |
| return; |
| } |
| |
| struct stat stat; |
| CHECK(GetInodeTable().GetStat(node->ino, &stat)); |
| CHECK_EQ(stat.st_ino, node->ino); |
| |
| if (S_ISDIR(stat.st_mode)) { |
| LOG(ERROR) << " opendir error: EISDIR"; |
| request->ReplyError(EISDIR); |
| return; |
| } |
| |
| LOG(INFO) << " " << OpenFlagsToString(fi->flags); |
| if ((fi->flags & O_ACCMODE) != O_RDONLY) { |
| LOG(ERROR) << " open error: EACCES"; |
| request->ReplyError(EACCES); |
| return; |
| } |
| |
| uint64_t handle = fusebox::OpenFile(); |
| LOG(INFO) << " opened fh " << handle; |
| request->ReplyOpen(handle); |
| } |
| |
| void FileSystemFake::Read(std::unique_ptr<BufferRequest> request, |
| fuse_ino_t ino, |
| size_t size, |
| off_t off, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "Read ino " << ino << " off " << off << " size " << size; |
| CHECK(fi); |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| if (!fusebox::GetFile(fi->fh)) { |
| LOG(ERROR) << " read error: EBADF"; |
| request->ReplyError(EBADF); |
| return; |
| } |
| |
| struct stat stat; |
| CHECK(GetInodeTable().GetStat(ino, &stat)); |
| CHECK_EQ(stat.st_ino, ino); |
| |
| if (S_ISDIR(stat.st_mode)) { |
| LOG(ERROR) << " opendir error: EISDIR"; |
| request->ReplyError(EISDIR); |
| return; |
| } |
| |
| const std::string data("hello\r\n"); |
| LOG(INFO) << " read fh " << fi->fh; |
| |
| const auto get_data_slice = [&data, &off, &size]() { |
| auto* source = data.data(); |
| auto length = data.size(); |
| if (off < length) |
| return base::StringPiece(source + off, std::min(length - off, size)); |
| return base::StringPiece(); |
| }; |
| |
| auto slice = get_data_slice(); |
| request->ReplyBuffer(slice.data(), slice.size()); |
| } |
| |
| void FileSystemFake::Release(std::unique_ptr<OkRequest> request, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| LOG(INFO) << "Release ino " << ino; |
| CHECK(fi); |
| |
| if (request->IsInterrupted()) |
| return; |
| |
| if (!fusebox::GetFile(fi->fh)) { |
| LOG(ERROR) << " release error: EBADF"; |
| request->ReplyError(EBADF); |
| return; |
| } |
| |
| LOG(INFO) << " release fh " << fi->fh; |
| fusebox::CloseFile(fi->fh); |
| request->ReplyOk(); |
| } |
| |
| } // namespace fusebox |