| // Copyright 2019 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 "arc/vm/vsock_proxy/proxy_file_system.h" |
| |
| #include <errno.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/optional.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_piece.h> |
| #include <base/synchronization/waitable_event.h> |
| #include <base/task_runner.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| |
| #include "arc/vm/vsock_proxy/fuse_mount.h" |
| |
| namespace arc { |
| namespace { |
| |
| constexpr char kFileSystemName[] = "arcvm-serverproxy"; |
| |
| // Returns ProxyFileSystem assigned to the FUSE's private_data. |
| ProxyFileSystem* GetFileSystem(fuse_req_t req) { |
| return static_cast<ProxyFileSystem*>(fuse_req_userdata(req)); |
| } |
| |
| void Lookup(fuse_req_t req, fuse_ino_t parent, const char* name) { |
| GetFileSystem(req)->Lookup(req, parent, name); |
| } |
| |
| void GetAttr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { |
| GetFileSystem(req)->GetAttr(req, ino, fi); |
| } |
| |
| void Open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { |
| GetFileSystem(req)->Open(req, ino, fi); |
| } |
| |
| void Read(fuse_req_t req, |
| fuse_ino_t ino, |
| size_t size, |
| off_t off, |
| struct fuse_file_info* fi) { |
| GetFileSystem(req)->Read(req, ino, size, off, fi); |
| } |
| |
| void Release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { |
| GetFileSystem(req)->Release(req, ino, fi); |
| } |
| |
| void ReadDir(fuse_req_t req, |
| fuse_ino_t ino, |
| size_t size, |
| off_t off, |
| struct fuse_file_info* fi) { |
| GetFileSystem(req)->ReadDir(req, ino, size, off, fi); |
| } |
| |
| } // namespace |
| |
| ProxyFileSystem::ProxyFileSystem( |
| Delegate* delegate, |
| scoped_refptr<base::TaskRunner> delegate_task_runner, |
| const base::FilePath& mount_path) |
| : delegate_(delegate), |
| delegate_task_runner_(delegate_task_runner), |
| mount_path_(mount_path) {} |
| |
| ProxyFileSystem::~ProxyFileSystem() { |
| if (init_task_runner_) { |
| base::WaitableEvent stopped( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| init_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](ProxyFileSystem* proxy_file_system, base::WaitableEvent* done) { |
| proxy_file_system->fuse_mount_ = nullptr; |
| done->Signal(); |
| }, |
| base::Unretained(this), &stopped)); |
| stopped.Wait(); |
| } |
| } |
| |
| bool ProxyFileSystem::Init() { |
| DCHECK(!init_task_runner_) << "Init can only be called once."; |
| init_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| |
| const std::string path_str = mount_path_.value(); |
| const char* fuse_argv[] = { |
| "", // Dummy argv[0]. |
| }; |
| |
| constexpr struct fuse_lowlevel_ops operations = { |
| .lookup = arc::Lookup, |
| .getattr = arc::GetAttr, |
| .open = arc::Open, |
| .read = arc::Read, |
| .release = arc::Release, |
| .readdir = arc::ReadDir, |
| }; |
| fuse_mount_ = std::make_unique<FuseMount>(mount_path_, kFileSystemName); |
| if (!fuse_mount_->Init(base::size(fuse_argv), const_cast<char**>(fuse_argv), |
| operations, this)) { |
| return false; |
| } |
| // TODO(hidehiko): Drop CAPS_SYS_ADMIN with minijail setup. |
| return true; |
| } |
| |
| void ProxyFileSystem::Lookup(fuse_req_t req, |
| fuse_ino_t parent, |
| const char* name) { |
| // The parent must be the root directory. |
| if (parent != 1) { |
| fuse_reply_err(req, ENOENT); |
| return; |
| } |
| |
| // Parse the name as inode; |
| uint64_t inode = 0; |
| if (!base::StringToUint64(name, &inode)) { |
| fuse_reply_err(req, ENOENT); |
| return; |
| } |
| struct fuse_entry_param entry = {}; |
| entry.ino = static_cast<fuse_ino_t>(inode); |
| entry.attr.st_mode = S_IFREG; |
| entry.attr.st_nlink = 1; |
| fuse_reply_entry(req, &entry); |
| } |
| |
| void ProxyFileSystem::GetAttr(fuse_req_t req, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| if (ino == 1) { // The root directory. |
| struct stat stat = {}; |
| stat.st_ino = ino, stat.st_mode = S_IFDIR, stat.st_nlink = 2, |
| fuse_reply_attr(req, &stat, 0); |
| return; |
| } |
| |
| auto state = GetState(ino); |
| if (!state.has_value()) { |
| LOG(ERROR) << "Inode not found: " << ino; |
| fuse_reply_err(req, ENOENT); |
| } |
| |
| struct stat stat = {}; |
| stat.st_ino = ino; |
| stat.st_mode = S_IFREG; |
| stat.st_nlink = 1; |
| if (!state->is_open) { |
| // If the file is not opened yet, this is called from kernel to open the |
| // file, which is initiated by the open(2) called in RegisterHandle() |
| // on |delegate_task_runner_|. |
| // Thus, we cannot make a blocking call to retrieve the size of the file, |
| // because it causes deadlock. Instead, we just fill '0', and return |
| // immediately. |
| stat.st_size = 0; |
| fuse_reply_attr(req, &stat, 0); |
| return; |
| } |
| delegate_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ProxyFileSystem::GetAttrInternal, base::Unretained(this), |
| req, state->handle, stat)); |
| } |
| |
| void ProxyFileSystem::GetAttrInternal(fuse_req_t req, |
| int64_t handle, |
| struct stat stat) { |
| delegate_->Fstat(handle, base::BindOnce( |
| [](fuse_req_t req, struct stat stat, |
| int error_code, int64_t size) { |
| if (error_code == 0) { |
| stat.st_size = size; |
| fuse_reply_attr(req, &stat, 0); |
| } else { |
| fuse_reply_err(req, error_code); |
| } |
| }, |
| req, stat)); |
| } |
| |
| void ProxyFileSystem::Open(fuse_req_t req, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| { |
| base::AutoLock lock(inode_lock_); |
| auto iter = inode_to_state_.find(ino); |
| if (iter == inode_to_state_.end()) { |
| LOG(ERROR) << "Inode not found: " << ino; |
| fuse_reply_err(req, ENOENT); |
| return; |
| } |
| iter->second.is_open = true; |
| |
| fi->direct_io = 1; |
| fi->fh = iter->second.handle; |
| } |
| fuse_reply_open(req, fi); |
| } |
| |
| void ProxyFileSystem::Read(fuse_req_t req, |
| fuse_ino_t ino, |
| size_t size, |
| off_t off, |
| struct fuse_file_info* fi) { |
| delegate_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ProxyFileSystem::ReadInternal, base::Unretained(this), |
| req, fi->fh, size, off)); |
| } |
| |
| void ProxyFileSystem::ReadInternal(fuse_req_t req, |
| int64_t handle, |
| size_t size, |
| off_t off) { |
| delegate_->Pread( |
| handle, size, off, |
| base::BindOnce( |
| [](fuse_req_t req, int error_code, const std::string& blob) { |
| if (error_code == 0) { |
| fuse_reply_buf(req, blob.data(), blob.size()); |
| } else { |
| fuse_reply_err(req, error_code); |
| } |
| }, |
| req)); |
| } |
| |
| void ProxyFileSystem::Release(fuse_req_t req, |
| fuse_ino_t ino, |
| struct fuse_file_info* fi) { |
| { |
| base::AutoLock lock(inode_lock_); |
| auto it = inode_to_state_.find(ino); |
| if (it == inode_to_state_.end()) { |
| LOG(ERROR) << "Inode not found: " << ino; |
| fuse_reply_err(req, ENOENT); |
| return; |
| } |
| inode_to_state_.erase(it); |
| } |
| |
| // |this| outlives |delegate_task_runner_|, so passing raw |this| pointer here |
| // is safe. |
| delegate_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce([](ProxyFileSystem* self, |
| int64_t handle) { self->delegate_->Close(handle); }, |
| this, fi->fh)); |
| fuse_reply_err(req, 0); |
| } |
| |
| void ProxyFileSystem::ReadDir(fuse_req_t req, |
| fuse_ino_t ino, |
| size_t size, |
| off_t off, |
| struct fuse_file_info* fi) { |
| // It must be the root directory. |
| if (ino != 1) { |
| fuse_reply_err(req, ENOTDIR); |
| return; |
| } |
| |
| // Just returns as if it is empty directory. |
| const char* kEntryNames[] = { |
| ".", |
| "..", |
| }; |
| std::vector<char> buf; |
| for (const char* entry_name : kEntryNames) { |
| const size_t offset = buf.size(); |
| // Make space for the entry. |
| const size_t entry_size = |
| fuse_add_direntry(req, nullptr, 0, entry_name, nullptr, 0); |
| buf.resize(buf.size() + entry_size); |
| // Add the entry to the buffer. |
| struct stat st = { |
| .st_ino = ino, |
| }; |
| fuse_add_direntry(req, buf.data() + offset, entry_size, entry_name, &st, |
| buf.size()); |
| } |
| // Send reply. |
| fuse_reply_buf(req, buf.data() + off, |
| std::min(buf.size() - static_cast<size_t>(off), size)); |
| } |
| |
| base::ScopedFD ProxyFileSystem::RegisterHandle(int64_t handle) { |
| fuse_ino_t inode = 0; |
| { |
| base::AutoLock lock(inode_lock_); |
| State state = { |
| .handle = handle, |
| .is_open = false, |
| }; |
| inode = next_inode_++; |
| if (!inode_to_state_.emplace(inode, state).second) { |
| LOG(ERROR) << "Failed to register inode: " << inode; |
| return {}; |
| } |
| } |
| |
| // Currently read-only file descriptor is only supported. |
| return base::ScopedFD(HANDLE_EINTR( |
| open(mount_path_.Append(base::NumberToString(inode)).value().c_str(), |
| O_RDONLY | O_CLOEXEC))); |
| } |
| |
| base::Optional<ProxyFileSystem::State> ProxyFileSystem::GetState( |
| fuse_ino_t inode) { |
| base::AutoLock lock_(inode_lock_); |
| auto iter = inode_to_state_.find(inode); |
| if (iter == inode_to_state_.end()) |
| return base::nullopt; |
| return iter->second; |
| } |
| |
| } // namespace arc |