blob: 8bf76649b6a82647b2e498d174173f06ee22a725 [file] [log] [blame]
// Copyright (c) 2012 The Chromium 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 "nacl_io/mount_fuse.h"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <algorithm>
#include "nacl_io/getdents_helper.h"
#include "nacl_io/kernel_handle.h"
#include "sdk_util/macros.h"
namespace nacl_io {
namespace {
struct FillDirInfo {
FillDirInfo(GetDentsHelper* getdents, size_t num_bytes)
: getdents(getdents), num_bytes(num_bytes), wrote_offset(false) {}
GetDentsHelper* getdents;
size_t num_bytes;
bool wrote_offset;
};
} // namespace
MountFuse::MountFuse() : fuse_ops_(NULL), fuse_user_data_(NULL) {}
Error MountFuse::Init(const MountInitArgs& args) {
Error error = Mount::Init(args);
if (error)
return error;
fuse_ops_ = args.fuse_ops;
if (fuse_ops_ == NULL)
return EINVAL;
if (fuse_ops_->init) {
struct fuse_conn_info info;
fuse_user_data_ = fuse_ops_->init(&info);
}
return 0;
}
void MountFuse::Destroy() {
if (fuse_ops_ && fuse_ops_->destroy)
fuse_ops_->destroy(fuse_user_data_);
}
Error MountFuse::Access(const Path& path, int a_mode) {
if (!fuse_ops_->access)
return ENOSYS;
int result = fuse_ops_->access(path.Join().c_str(), a_mode);
if (result < 0)
return -result;
return 0;
}
Error MountFuse::Open(const Path& path,
int open_flags,
ScopedMountNode* out_node) {
std::string path_str = path.Join();
const char* path_cstr = path_str.c_str();
int result = 0;
struct fuse_file_info fi;
memset(&fi, 0, sizeof(fi));
fi.flags = open_flags;
if (open_flags & (O_CREAT | O_EXCL)) {
// According to the FUSE docs, open() is not called when O_CREAT or O_EXCL
// is passed.
mode_t mode = S_IRALL | S_IWALL;
if (fuse_ops_->create) {
result = fuse_ops_->create(path_cstr, mode, &fi);
if (result < 0)
return -result;
} else if (fuse_ops_->mknod) {
result = fuse_ops_->mknod(path_cstr, mode, dev_);
if (result < 0)
return -result;
} else {
return ENOSYS;
}
} else {
// First determine if this is a regular file or a directory.
if (fuse_ops_->getattr) {
struct stat statbuf;
result = fuse_ops_->getattr(path_cstr, &statbuf);
if (result < 0)
return -result;
if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
// This is a directory. Don't try to open, just create a new node with
// this path.
ScopedMountNode node(
new MountNodeFuseDir(this, fuse_ops_, fi, path_cstr));
Error error = node->Init(open_flags);
if (error)
return error;
*out_node = node;
return 0;
}
}
// Existing file.
if (open_flags & O_TRUNC) {
// According to the FUSE docs, O_TRUNC does two calls: first truncate()
// then open().
if (!fuse_ops_->truncate)
return ENOSYS;
result = fuse_ops_->truncate(path_cstr, 0);
if (result < 0)
return -result;
}
if (!fuse_ops_->open)
return ENOSYS;
result = fuse_ops_->open(path_cstr, &fi);
if (result < 0)
return -result;
}
ScopedMountNode node(new MountNodeFuseFile(this, fuse_ops_, fi, path_cstr));
Error error = node->Init(open_flags);
if (error)
return error;
*out_node = node;
return 0;
}
Error MountFuse::Unlink(const Path& path) {
if (!fuse_ops_->unlink)
return ENOSYS;
int result = fuse_ops_->unlink(path.Join().c_str());
if (result < 0)
return -result;
return 0;
}
Error MountFuse::Mkdir(const Path& path, int perm) {
if (!fuse_ops_->mkdir)
return ENOSYS;
int result = fuse_ops_->mkdir(path.Join().c_str(), perm);
if (result < 0)
return -result;
return 0;
}
Error MountFuse::Rmdir(const Path& path) {
if (!fuse_ops_->rmdir)
return ENOSYS;
int result = fuse_ops_->rmdir(path.Join().c_str());
if (result < 0)
return -result;
return 0;
}
Error MountFuse::Remove(const Path& path) {
ScopedMountNode node;
Error error = Open(path, O_RDONLY, &node);
if (error)
return error;
struct stat statbuf;
error = node->GetStat(&statbuf);
if (error)
return error;
node.reset();
if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
return Rmdir(path);
} else {
return Unlink(path);
}
}
Error MountFuse::Rename(const Path& path, const Path& newpath) {
if (!fuse_ops_->rename)
return ENOSYS;
int result = fuse_ops_->rename(path.Join().c_str(), newpath.Join().c_str());
if (result < 0)
return -result;
return 0;
}
MountNodeFuse::MountNodeFuse(Mount* mount,
struct fuse_operations* fuse_ops,
struct fuse_file_info& info,
const std::string& path)
: MountNode(mount),
fuse_ops_(fuse_ops),
info_(info),
path_(path) {}
bool MountNodeFuse::CanOpen(int open_flags) {
struct stat statbuf;
Error error = GetStat(&statbuf);
if (error)
return false;
// GetStat cached the mode in stat_.st_mode. Forward to MountNode::CanOpen,
// which will check this mode against open_flags.
return MountNode::CanOpen(open_flags);
}
Error MountNodeFuse::GetStat(struct stat* stat) {
int result;
if (fuse_ops_->fgetattr) {
result = fuse_ops_->fgetattr(path_.c_str(), stat, &info_);
if (result < 0)
return -result;
} else if (fuse_ops_->getattr) {
result = fuse_ops_->getattr(path_.c_str(), stat);
if (result < 0)
return -result;
} else {
return ENOSYS;
}
// Also update the cached stat values.
stat_ = *stat;
return 0;
}
Error MountNodeFuse::VIoctl(int request, va_list args) {
// TODO(binji): implement
return ENOSYS;
}
Error MountNodeFuse::Tcflush(int queue_selector) {
// TODO(binji): use ioctl for this?
return ENOSYS;
}
Error MountNodeFuse::Tcgetattr(struct termios* termios_p) {
// TODO(binji): use ioctl for this?
return ENOSYS;
}
Error MountNodeFuse::Tcsetattr(int optional_actions,
const struct termios* termios_p) {
// TODO(binji): use ioctl for this?
return ENOSYS;
}
Error MountNodeFuse::GetSize(size_t* out_size) {
struct stat statbuf;
Error error = GetStat(&statbuf);
if (error)
return error;
*out_size = stat_.st_size;
return 0;
}
MountNodeFuseFile::MountNodeFuseFile(Mount* mount,
struct fuse_operations* fuse_ops,
struct fuse_file_info& info,
const std::string& path)
: MountNodeFuse(mount, fuse_ops, info, path) {}
void MountNodeFuseFile::Destroy() {
if (!fuse_ops_->release)
return;
fuse_ops_->release(path_.c_str(), &info_);
}
Error MountNodeFuseFile::FSync() {
if (!fuse_ops_->fsync)
return ENOSYS;
int datasync = 0;
int result = fuse_ops_->fsync(path_.c_str(), datasync, &info_);
if (result < 0)
return -result;
return 0;
}
Error MountNodeFuseFile::FTruncate(off_t length) {
if (!fuse_ops_->ftruncate)
return ENOSYS;
int result = fuse_ops_->ftruncate(path_.c_str(), length, &info_);
if (result < 0)
return -result;
return 0;
}
Error MountNodeFuseFile::Read(const HandleAttr& attr,
void* buf,
size_t count,
int* out_bytes) {
if (!fuse_ops_->read)
return ENOSYS;
char* cbuf = static_cast<char*>(buf);
int result = fuse_ops_->read(path_.c_str(), cbuf, count, attr.offs, &info_);
if (result < 0)
return -result;
// Fuse docs say that a read() call will always completely fill the buffer
// (padding with zeroes) unless the direct_io mount flag is set.
// TODO(binji): support the direct_io flag
if (static_cast<size_t>(result) < count)
memset(&cbuf[result], 0, count - result);
*out_bytes = count;
return 0;
}
Error MountNodeFuseFile::Write(const HandleAttr& attr,
const void* buf,
size_t count,
int* out_bytes) {
if (!fuse_ops_->write)
return ENOSYS;
int result = fuse_ops_->write(
path_.c_str(), static_cast<const char*>(buf), count, attr.offs, &info_);
if (result < 0)
return -result;
// Fuse docs say that a write() call will always write the entire buffer
// unless the direct_io mount flag is set.
// TODO(binji): What should we do if the user breaks this contract? Warn?
// TODO(binji): support the direct_io flag
*out_bytes = result;
return 0;
}
MountNodeFuseDir::MountNodeFuseDir(Mount* mount,
struct fuse_operations* fuse_ops,
struct fuse_file_info& info,
const std::string& path)
: MountNodeFuse(mount, fuse_ops, info, path) {}
void MountNodeFuseDir::Destroy() {
if (!fuse_ops_->releasedir)
return;
fuse_ops_->releasedir(path_.c_str(), &info_);
}
Error MountNodeFuseDir::FSync() {
if (!fuse_ops_->fsyncdir)
return ENOSYS;
int datasync = 0;
int result = fuse_ops_->fsyncdir(path_.c_str(), datasync, &info_);
if (result < 0)
return -result;
return 0;
}
Error MountNodeFuseDir::GetDents(size_t offs,
struct dirent* pdir,
size_t count,
int* out_bytes) {
if (!fuse_ops_->readdir)
return ENOSYS;
bool opened_dir = false;
int result;
// Opendir is not necessary, only readdir. Call it anyway, if it is defined.
if (fuse_ops_->opendir) {
result = fuse_ops_->opendir(path_.c_str(), &info_);
if (result < 0)
return -result;
opened_dir = true;
}
Error error = 0;
GetDentsHelper getdents;
FillDirInfo fill_info(&getdents, count);
result = fuse_ops_->readdir(path_.c_str(),
&fill_info,
&MountNodeFuseDir::FillDirCallback,
offs,
&info_);
if (result < 0)
goto fail;
// If the fill function ever wrote an entry with |offs| != 0, then assume it
// was not given the full list of entries. In that case, GetDentsHelper's
// buffers start with the entry at offset |offs|, so the call to
// GetDentsHelpers::GetDents should use an offset of 0.
if (fill_info.wrote_offset)
offs = 0;
// The entries have been filled in from the FUSE filesystem, now write them
// out to the buffer.
error = getdents.GetDents(offs, pdir, count, out_bytes);
if (error)
goto fail;
return 0;
fail:
if (opened_dir && fuse_ops_->releasedir) {
// Ignore this result, we're already failing.
fuse_ops_->releasedir(path_.c_str(), &info_);
}
return -result;
}
int MountNodeFuseDir::FillDirCallback(void* buf,
const char* name,
const struct stat* stbuf,
off_t off) {
FillDirInfo* fill_info = static_cast<FillDirInfo*>(buf);
// It is OK for the FUSE filesystem to pass a NULL stbuf. In that case, just
// use a bogus ino.
ino_t ino = stbuf ? stbuf->st_ino : 1;
// The FUSE docs say that the implementor of readdir can choose to ignore the
// offset given, and instead return all entries. To do this, they pass
// |off| == 0 for each call.
if (off) {
if (fill_info->num_bytes < sizeof(dirent))
return 1; // 1 => buffer is full
fill_info->wrote_offset = true;
fill_info->getdents->AddDirent(ino, name, strlen(name));
fill_info->num_bytes -= sizeof(dirent);
// return 0 => request more data. return 1 => buffer full.
return fill_info->num_bytes > 0 ? 0 : 1;
} else {
fill_info->getdents->AddDirent(ino, name, strlen(name));
fill_info->num_bytes -= sizeof(dirent);
// According to the docs, we can never return 1 (buffer full) when the
// offset is zero (the user is probably ignoring the result anyway).
return 0;
}
}
} // namespace nacl_io