blob: fa1b26424a114e7158732dd517a2a6052e3f412b [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <digest/digest.h>
#include <zircon/device/device.h>
#include <zircon/device/vfs.h>
#include <fbl/ref_ptr.h>
#include <lib/fdio/debug.h>
#include <lib/fdio/vfs.h>
#include <sync/completion.h>
#include <zircon/syscalls.h>
#define ZXDEBUG 0
#include <blobfs/blobfs.h>
using digest::Digest;
namespace blobfs {
void VnodeBlob::fbl_recycle() {
if (GetState() != kBlobStatePurged && !IsDirectory()) {
// Relocate blobs which haven't been deleted to the closed cache.
blobfs_->VnodeReleaseSoft(this);
} else {
// Destroy blobs which have been purged.
delete this;
}
}
void VnodeBlob::TearDown() {
ZX_ASSERT(clone_watcher_.object() == ZX_HANDLE_INVALID);
if (blob_ != nullptr) {
blobfs_->DetachVmo(vmoid_);
}
blob_ = nullptr;
}
VnodeBlob::~VnodeBlob() {
TearDown();
}
zx_status_t VnodeBlob::ValidateFlags(uint32_t flags) {
if ((flags & ZX_FS_FLAG_DIRECTORY) && !IsDirectory()) {
return ZX_ERR_NOT_DIR;
}
if (flags & ZX_FS_RIGHT_WRITABLE) {
if (IsDirectory()) {
return ZX_ERR_NOT_FILE;
} else if (GetState() != kBlobStateEmpty) {
return ZX_ERR_ACCESS_DENIED;
}
}
return ZX_OK;
}
zx_status_t VnodeBlob::Readdir(fs::vdircookie_t* cookie, void* dirents, size_t len,
size_t* out_actual) {
if (!IsDirectory()) {
return ZX_ERR_NOT_DIR;
}
return blobfs_->Readdir(cookie, dirents, len, out_actual);
}
zx_status_t VnodeBlob::Read(void* data, size_t len, size_t off, size_t* out_actual) {
TRACE_DURATION("blobfs", "VnodeBlob::Read", "len", len, "off", off);
if (IsDirectory()) {
return ZX_ERR_NOT_FILE;
}
return ReadInternal(data, len, off, out_actual);
}
zx_status_t VnodeBlob::Write(const void* data, size_t len, size_t offset,
size_t* out_actual) {
TRACE_DURATION("blobfs", "VnodeBlob::Write", "len", len, "off", offset);
if (IsDirectory()) {
return ZX_ERR_NOT_FILE;
}
return WriteInternal(data, len, out_actual);
}
zx_status_t VnodeBlob::Append(const void* data, size_t len, size_t* out_end,
size_t* out_actual) {
zx_status_t status = WriteInternal(data, len, out_actual);
if (GetState() == kBlobStateDataWrite) {
ZX_DEBUG_ASSERT(write_info_ != nullptr);
*out_actual = write_info_->bytes_written;
} else {
*out_actual = inode_.blob_size;
}
return status;
}
zx_status_t VnodeBlob::Lookup(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name) {
TRACE_DURATION("blobfs", "VnodeBlob::Lookup", "name", name);
assert(memchr(name.data(), '/', name.length()) == nullptr);
if (name == "." && IsDirectory()) {
// Special case: Accessing root directory via '.'
*out = fbl::RefPtr<VnodeBlob>(this);
return ZX_OK;
}
if (!IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status;
Digest digest;
if ((status = digest.Parse(name.data(), name.length())) != ZX_OK) {
return status;
}
fbl::RefPtr<VnodeBlob> vn;
if ((status = blobfs_->LookupBlob(digest, &vn)) < 0) {
return status;
}
*out = fbl::move(vn);
return ZX_OK;
}
zx_status_t VnodeBlob::Getattr(vnattr_t* a) {
memset(a, 0, sizeof(vnattr_t));
a->mode = (IsDirectory() ? V_TYPE_DIR : V_TYPE_FILE) | V_IRUSR;
a->inode = 0;
a->size = IsDirectory() ? 0 : SizeData();
a->blksize = kBlobfsBlockSize;
a->blkcount = inode_.num_blocks * (kBlobfsBlockSize / VNATTR_BLKSIZE);
a->nlink = 1;
a->create_time = 0;
a->modify_time = 0;
return ZX_OK;
}
zx_status_t VnodeBlob::Create(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name, uint32_t mode) {
TRACE_DURATION("blobfs", "VnodeBlob::Create", "name", name, "mode", mode);
assert(memchr(name.data(), '/', name.length()) == nullptr);
if (!IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
Digest digest;
zx_status_t status;
if ((status = digest.Parse(name.data(), name.length())) != ZX_OK) {
return status;
}
fbl::RefPtr<VnodeBlob> vn;
if ((status = blobfs_->NewBlob(digest, &vn)) != ZX_OK) {
return status;
}
vn->fd_count_ = 1;
*out = fbl::move(vn);
return ZX_OK;
}
constexpr const char kFsName[] = "blobfs";
zx_status_t VnodeBlob::Ioctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
size_t out_len, size_t* out_actual) {
switch (op) {
case IOCTL_VFS_QUERY_FS: {
if (out_len < sizeof(vfs_query_info_t) + strlen(kFsName)) {
return ZX_ERR_INVALID_ARGS;
}
vfs_query_info_t* info = static_cast<vfs_query_info_t*>(out_buf);
memset(info, 0, sizeof(*info));
info->block_size = kBlobfsBlockSize;
info->max_filename_size = Digest::kLength * 2;
info->fs_type = VFS_TYPE_BLOBFS;
info->fs_id = blobfs_->GetFsId();
info->total_bytes = blobfs_->info_.block_count * blobfs_->info_.block_size;
info->used_bytes = blobfs_->info_.alloc_block_count * blobfs_->info_.block_size;
info->total_nodes = blobfs_->info_.inode_count;
info->used_nodes = blobfs_->info_.alloc_inode_count;
memcpy(info->name, kFsName, strlen(kFsName));
*out_actual = sizeof(vfs_query_info_t) + strlen(kFsName);
return ZX_OK;
}
#ifdef __Fuchsia__
case IOCTL_VFS_GET_DEVICE_PATH: {
ssize_t len = ioctl_device_get_topo_path(blobfs_->Fd(), static_cast<char*>(out_buf), out_len);
if ((ssize_t)out_len < len) {
return ZX_ERR_INVALID_ARGS;
}
if (len >= 0) {
*out_actual = len;
}
return len > 0 ? ZX_OK : static_cast<zx_status_t>(len);
}
#endif
default: {
return ZX_ERR_NOT_SUPPORTED;
}
}
}
zx_status_t VnodeBlob::Truncate(size_t len) {
TRACE_DURATION("blobfs", "VnodeBlob::Truncate", "len", len);
if (IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
return SpaceAllocate(len);
}
zx_status_t VnodeBlob::Unlink(fbl::StringPiece name, bool must_be_dir) {
TRACE_DURATION("blobfs", "VnodeBlob::Unlink", "name", name, "must_be_dir", must_be_dir);
assert(memchr(name.data(), '/', name.length()) == nullptr);
if (!IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status;
Digest digest;
fbl::RefPtr<VnodeBlob> out;
if ((status = digest.Parse(name.data(), name.length())) != ZX_OK) {
return status;
} else if ((status = blobfs_->LookupBlob(digest, &out)) < 0) {
return status;
}
out->QueueUnlink();
return ZX_OK;
}
zx_status_t VnodeBlob::GetVmo(int flags, zx_handle_t* out) {
TRACE_DURATION("blobfs", "VnodeBlob::GetVmo", "flags", flags);
if (IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
if (flags & FDIO_MMAP_FLAG_WRITE) {
return ZX_ERR_NOT_SUPPORTED;
} else if (flags & FDIO_MMAP_FLAG_EXACT) {
return ZX_ERR_NOT_SUPPORTED;
}
// Let clients map and set the names of their VMOs.
zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHTS_PROPERTY;
// We can ignore FDIO_MMAP_FLAG_PRIVATE, since private / shared access
// to the underlying VMO can both be satisfied with a clone due to
// the immutability of blobfs blobs.
rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0;
rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0;
return CloneVmo(rights, out);
}
void VnodeBlob::Sync(SyncCallback closure) {
if (atomic_load(&syncing_)) {
blobfs_->Sync([this, cb = fbl::move(closure)](zx_status_t status) {
if (status != ZX_OK) {
cb(status);
return;
}
status = fsync(blobfs_->Fd());
cb(status);
});
} else {
closure(ZX_OK);
}
}
void VnodeBlob::CompleteSync() {
fsync(blobfs_->Fd());
atomic_store(&syncing_, false);
}
fbl::RefPtr<VnodeBlob> VnodeBlob::CloneWatcherTeardown() {
if (clone_watcher_.is_pending()) {
clone_watcher_.Cancel();
clone_watcher_.set_object(ZX_HANDLE_INVALID);
return fbl::move(clone_ref_);
}
return nullptr;
}
zx_status_t VnodeBlob::Open(uint32_t flags, fbl::RefPtr<Vnode>* out_redirect) {
fd_count_++;
return ZX_OK;
}
zx_status_t VnodeBlob::Close() {
ZX_DEBUG_ASSERT_MSG(fd_count_ > 0, "Closing blob with no fds open");
fd_count_--;
// Attempt purge in case blob was unlinked prior to close
TryPurge();
return ZX_OK;
}
void VnodeBlob::Purge() {
ZX_DEBUG_ASSERT(fd_count_ == 0);
ZX_DEBUG_ASSERT(Purgeable());
blobfs_->PurgeBlob(this);
SetState(kBlobStatePurged);
}
} // namespace blobfs