blob: 54df79230a6d5ec13cad59e16eece3c33186fbc6 [file] [log] [blame]
// 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(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
~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_;
};
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