| // 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 "smbfs/fuse_session.h" |
| |
| #include <errno.h> |
| #include <fuse_opt.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/posix/safe_strerror.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "smbfs/filesystem.h" |
| #include "smbfs/request.h" |
| #include "smbfs/util.h" |
| |
| namespace smbfs { |
| |
| class FuseSession::Impl { |
| public: |
| Impl(FuseSession* session, std::unique_ptr<Filesystem> fs) |
| : session_(session), fs_(std::move(fs)) { |
| DCHECK(fs_); |
| } |
| ~Impl() = default; |
| |
| // FUSE low-level operations. See fuse_lowlevel_ops in fuse_lowlevel.h for |
| // details. |
| static void FuseDestroy(void* userdata) { |
| static_cast<Impl*>(userdata)->Destroy(); |
| } |
| |
| static void FuseStatFs(fuse_req_t request, fuse_ino_t inode) { |
| static_cast<Impl*>(fuse_req_userdata(request))->StatFs(request, inode); |
| } |
| |
| static void FuseLookup(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Lookup(request, parent_inode, name); |
| } |
| |
| static void FuseForget(fuse_req_t request, |
| fuse_ino_t inode, |
| unsigned long count) { // NOLINT(runtime/int) |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Forget(request, inode, count); |
| } |
| |
| static void FuseGetAttr(fuse_req_t request, |
| fuse_ino_t inode, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->GetAttr(request, inode, info); |
| } |
| |
| static void FuseSetAttr(fuse_req_t request, |
| fuse_ino_t inode, |
| struct stat* attr, |
| int to_set, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->SetAttr(request, inode, attr, to_set, info); |
| } |
| |
| static void FuseOpen(fuse_req_t request, |
| fuse_ino_t inode, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request))->Open(request, inode, info); |
| } |
| |
| static void FuseCreate(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name, |
| mode_t mode, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Create(request, parent_inode, name, mode, info); |
| } |
| |
| static void FuseRead(fuse_req_t request, |
| fuse_ino_t inode, |
| size_t size, |
| off_t off, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Read(request, inode, size, off, info); |
| } |
| |
| static void FuseWrite(fuse_req_t request, |
| fuse_ino_t inode, |
| const char* buf, |
| size_t size, |
| off_t off, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Write(request, inode, buf, size, off, info); |
| } |
| |
| static void FuseRelease(fuse_req_t request, |
| fuse_ino_t inode, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Release(request, inode, info); |
| } |
| |
| static void FuseRename(fuse_req_t request, |
| fuse_ino_t old_parent, |
| const char* old_name, |
| fuse_ino_t new_parent, |
| const char* new_name) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Rename(request, old_parent, old_name, new_parent, new_name); |
| } |
| |
| static void FuseUnlink(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->Unlink(request, parent_inode, name); |
| } |
| |
| static void FuseOpenDir(fuse_req_t request, |
| fuse_ino_t inode, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->OpenDir(request, inode, info); |
| } |
| |
| static void FuseReadDir(fuse_req_t request, |
| fuse_ino_t inode, |
| size_t size, |
| off_t off, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->ReadDir(request, inode, size, off, info); |
| } |
| |
| static void FuseReleaseDir(fuse_req_t request, |
| fuse_ino_t inode, |
| fuse_file_info* info) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->ReleaseDir(request, inode, info); |
| } |
| |
| static void FuseMkDir(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name, |
| mode_t mode) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->MkDir(request, parent_inode, name, mode); |
| } |
| |
| static void FuseRmDir(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name) { |
| static_cast<Impl*>(fuse_req_userdata(request)) |
| ->RmDir(request, parent_inode, name); |
| } |
| |
| private: |
| void Destroy() { |
| VLOG(1) << "FuseSession::Destroy"; |
| session_->RequestStop(); |
| } |
| |
| void StatFs(fuse_req_t request, fuse_ino_t inode) { |
| VLOG(1) << "FuseSession::StatFs inode: " << inode; |
| fs_->StatFs(std::make_unique<StatFsRequest>(request), inode); |
| } |
| |
| void Lookup(fuse_req_t request, fuse_ino_t parent_inode, const char* name) { |
| VLOG(1) << "FuseSession::Lookup parent: " << parent_inode |
| << " name:" << name; |
| fs_->Lookup(std::make_unique<EntryRequest>(request), parent_inode, name); |
| } |
| |
| void Forget(fuse_req_t request, |
| fuse_ino_t inode, |
| unsigned long count) { // NOLINT(runtime/int) |
| VLOG(1) << "FuseSession::Forget inode: " << inode << " count:" << count; |
| fs_->Forget(inode, count); |
| fuse_reply_none(request); |
| } |
| |
| void GetAttr(fuse_req_t request, |
| fuse_ino_t inode, |
| fuse_file_info* unused_info) { |
| VLOG(1) << "FuseSession::GetAttr: " << inode; |
| fs_->GetAttr(std::make_unique<AttrRequest>(request), inode); |
| } |
| |
| void SetAttr(fuse_req_t request, |
| fuse_ino_t inode, |
| struct stat* attr, |
| int to_set, |
| fuse_file_info* info) { |
| VLOG(1) << "FuseSession::SetAttr: " << inode |
| << " to_set: " << ToSetFlagsToString(to_set) |
| << " handle: " << (info ? info->fh : 0); |
| fs_->SetAttr(std::make_unique<AttrRequest>(request), inode, |
| info ? info->fh : base::Optional<uint64_t>(), *attr, to_set); |
| } |
| |
| void Open(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { |
| VLOG(1) << "FuseSession::Open inode: " << inode |
| << " flags: " << OpenFlagsToString(info->flags); |
| fs_->Open(std::make_unique<OpenRequest>(request), inode, info->flags); |
| } |
| |
| void Create(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name, |
| mode_t mode, |
| fuse_file_info* info) { |
| VLOG(1) << "FuseSession::Create parent: " << parent_inode |
| << " name: " << name << " mode: " << base::StringPrintf("0%o", mode) |
| << " flags: " << OpenFlagsToString(info->flags); |
| fs_->Create(std::make_unique<CreateRequest>(request), parent_inode, name, |
| mode, info->flags); |
| } |
| |
| void Read(fuse_req_t request, |
| fuse_ino_t inode, |
| size_t size, |
| off_t off, |
| fuse_file_info* info) { |
| VLOG(1) << "FuseSession::Read inode: " << inode << " handle:" << info->fh |
| << " offset: " << off << " size: " << size; |
| fs_->Read(std::make_unique<BufRequest>(request), inode, info->fh, size, |
| off); |
| } |
| |
| void Write(fuse_req_t request, |
| fuse_ino_t inode, |
| const char* buf, |
| size_t size, |
| off_t off, |
| fuse_file_info* info) { |
| VLOG(1) << "FuseSession::Write inode: " << inode << " handle:" << info->fh |
| << " offset: " << off << " size: " << size; |
| fs_->Write(std::make_unique<WriteRequest>(request), inode, info->fh, buf, |
| size, off); |
| } |
| |
| void Release(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { |
| VLOG(1) << "FuseSession::Release inode: " << inode |
| << " handle:" << info->fh; |
| fs_->Release(std::make_unique<SimpleRequest>(request), inode, info->fh); |
| } |
| |
| void Rename(fuse_req_t request, |
| fuse_ino_t old_parent, |
| const char* old_name, |
| fuse_ino_t new_parent, |
| const char* new_name) { |
| VLOG(1) << "FuseSession::Rename old_parent: " << old_parent |
| << " old_name: " << old_name << " new_parent: " << new_parent |
| << " new_name: " << new_name; |
| fs_->Rename(std::make_unique<SimpleRequest>(request), old_parent, old_name, |
| new_parent, new_name); |
| } |
| |
| void Unlink(fuse_req_t request, fuse_ino_t parent_inode, const char* name) { |
| VLOG(1) << "FuseSession::Unlink parent_inode: " << parent_inode |
| << " name: " << name; |
| fs_->Unlink(std::make_unique<SimpleRequest>(request), parent_inode, name); |
| } |
| |
| void OpenDir(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { |
| VLOG(1) << "FuseSession::OpenDir inode: " << inode |
| << " flags: " << OpenFlagsToString(info->flags); |
| fs_->OpenDir(std::make_unique<OpenRequest>(request), inode, info->flags); |
| } |
| |
| void ReadDir(fuse_req_t request, |
| fuse_ino_t inode, |
| size_t size, |
| off_t off, |
| fuse_file_info* info) { |
| VLOG(1) << "FuseSession::ReadDir inode: " << inode << " handle:" << info->fh |
| << " offset: " << off; |
| fs_->ReadDir(std::make_unique<DirentryRequest>(request, size), inode, |
| info->fh, off); |
| } |
| |
| void ReleaseDir(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { |
| VLOG(1) << "FuseSession::ReleaseDir inode: " << inode |
| << " handle:" << info->fh; |
| fs_->ReleaseDir(std::make_unique<SimpleRequest>(request), inode, info->fh); |
| } |
| |
| void MkDir(fuse_req_t request, |
| fuse_ino_t parent_inode, |
| const char* name, |
| mode_t mode) { |
| VLOG(1) << "FuseSession::MkDir parent_inode: " << parent_inode |
| << " name:" << name << " mode: " << base::StringPrintf("0%o", mode); |
| fs_->MkDir(std::make_unique<EntryRequest>(request), parent_inode, name, |
| mode); |
| } |
| |
| void RmDir(fuse_req_t request, fuse_ino_t parent_inode, const char* name) { |
| VLOG(1) << "FuseSession::RmDir parent_inode: " << parent_inode |
| << " name:" << name; |
| fs_->RmDir(std::make_unique<SimpleRequest>(request), parent_inode, name); |
| } |
| |
| FuseSession* session_; |
| std::unique_ptr<Filesystem> fs_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Impl); |
| }; |
| |
| FuseSession::FuseSession(std::unique_ptr<Filesystem> fs, fuse_chan* chan) |
| : impl_(std::make_unique<Impl>(this, std::move(fs))), chan_(chan) { |
| DCHECK(chan_); |
| } |
| |
| FuseSession::~FuseSession() { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| |
| // Ensure |stop_callback_| isn't called as a result of destruction. |
| stop_callback_.Reset(); |
| |
| if (session_) { |
| // Disconnect the channel from the fuse_session before destroying them both. |
| // fuse_session_destroy() also destroys the attached channel (not |
| // documented). Disconnecting the two simplifies logic, and ensures |
| // FuseSession maintains ownership of the fuse_chan. |
| fuse_session_remove_chan(chan_); |
| fuse_session_destroy(session_); |
| } |
| fuse_chan_destroy(chan_); |
| } |
| |
| bool FuseSession::Start(base::OnceClosure stop_callback) { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| CHECK_EQ(session_, nullptr); |
| |
| fuse_lowlevel_ops ops = {0}; |
| ops.destroy = &Impl::FuseDestroy; |
| ops.statfs = &Impl::FuseStatFs; |
| ops.lookup = &Impl::FuseLookup; |
| ops.forget = &Impl::FuseForget; |
| ops.getattr = &Impl::FuseGetAttr; |
| ops.setattr = &Impl::FuseSetAttr; |
| ops.open = &Impl::FuseOpen; |
| ops.create = &Impl::FuseCreate; |
| ops.read = &Impl::FuseRead; |
| ops.write = &Impl::FuseWrite; |
| ops.release = &Impl::FuseRelease; |
| ops.rename = &Impl::FuseRename; |
| ops.unlink = &Impl::FuseUnlink; |
| ops.opendir = &Impl::FuseOpenDir; |
| ops.readdir = &Impl::FuseReadDir; |
| ops.releasedir = &Impl::FuseReleaseDir; |
| ops.mkdir = &Impl::FuseMkDir; |
| ops.rmdir = &Impl::FuseRmDir; |
| |
| struct fuse_args args = {0}; |
| // The first argument needs to be the program name, which is ignored by the |
| // options parser. |
| CHECK_EQ(fuse_opt_add_arg(&args, "smbfs"), 0); |
| CHECK_EQ(fuse_opt_add_arg(&args, "-obig_writes"), 0); |
| |
| session_ = fuse_lowlevel_new(&args, &ops, sizeof(ops), impl_.get()); |
| if (!session_) { |
| LOG(ERROR) << "Unable to create new FUSE session"; |
| return false; |
| } |
| fuse_session_add_chan(session_, chan_); |
| read_buffer_.resize(fuse_chan_bufsize(chan_)); |
| |
| CHECK(base::SetNonBlocking(fuse_chan_fd(chan_))); |
| read_watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| fuse_chan_fd(chan_), base::BindRepeating(&FuseSession::OnChannelReadable, |
| base::Unretained(this))); |
| stop_callback_ = std::move(stop_callback); |
| |
| return true; |
| } |
| |
| void FuseSession::OnChannelReadable() { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| VLOG(2) << "FuseSession::OnChannelReadable"; |
| fuse_buf buf = {0}; |
| buf.mem = read_buffer_.data(); |
| buf.size = read_buffer_.size(); |
| fuse_chan* temp_chan = chan_; |
| int read_size = fuse_session_receive_buf(session_, &buf, &temp_chan); |
| if (read_size == 0) { |
| // A read of 0 indicates the filesystem has been unmounted and the kernel |
| // driver has closed the fuse session. |
| LOG(INFO) << "FUSE kernel channel closed, shutting down"; |
| RequestStop(); |
| return; |
| } else if (read_size == -EINTR) { |
| // Since FD watching is level-triggered, this function will get called again |
| // very soon to try again. |
| return; |
| } else if (read_size < 0) { |
| LOG(ERROR) << "FUSE channel read failed with error: " |
| << base::safe_strerror(-read_size) << " [" << -read_size |
| << "], shutting down"; |
| RequestStop(); |
| return; |
| } |
| |
| fuse_session_process_buf(session_, &buf, chan_); |
| } |
| |
| void FuseSession::RequestStop() { |
| read_watcher_.reset(); |
| |
| if (stop_callback_) { |
| std::move(stop_callback_).Run(); |
| } |
| } |
| |
| } // namespace smbfs |