blob: 9d559a0bcb76724cf4bad2ab09a2525f50447b8b [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 <zircon/device/device.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include <fdio/debug.h>
#include <fdio/vfs.h>
#include <fbl/ref_ptr.h>
#define MXDEBUG 0
#include "blobstore-private.h"
using digest::Digest;
namespace blobstore {
VnodeBlob::~VnodeBlob() {
blobstore_->ReleaseBlob(this);
if (blob_ != nullptr) {
block_fifo_request_t request;
request.txnid = blobstore_->TxnId();
request.vmoid = vmoid_;
request.opcode = BLOCKIO_CLOSE_VMO;
blobstore_->Txn(&request, 1);
}
}
zx_status_t VnodeBlob::ValidateFlags(uint32_t flags) {
if ((flags & O_DIRECTORY) && !IsDirectory()) {
return ZX_ERR_NOT_DIR;
}
switch (flags & O_ACCMODE) {
case O_WRONLY:
case O_RDWR:
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 blobstore_->Readdir(cookie, dirents, len, out_actual);
}
zx_status_t VnodeBlob::Read(void* data, size_t len, size_t off, size_t* out_actual) {
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) {
if (IsDirectory()) {
return ZX_ERR_NOT_FILE;
}
zx_status_t status = WriteInternal(data, len, out_actual);
return status;
}
zx_status_t VnodeBlob::Append(const void* data, size_t len, size_t* out_end,
size_t* out_actual) {
zx_status_t status = Write(data, len, bytes_written_, out_actual);
*out_actual = bytes_written_;
return status;
}
zx_status_t VnodeBlob::Lookup(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece 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 = blobstore_->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 = kBlobstoreBlockSize;
a->blkcount = blobstore_->GetNode(map_index_)->num_blocks *
(kBlobstoreBlockSize / 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) {
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 = blobstore_->NewBlob(digest, &vn)) != ZX_OK) {
return status;
}
*out = fbl::move(vn);
return ZX_OK;
}
constexpr const char kFsName[] = "blobstore";
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);
info->total_bytes = blobstore_->info_.block_count * blobstore_->info_.block_size;
info->used_bytes = blobstore_->info_.alloc_block_count * blobstore_->info_.block_size;
info->total_nodes = blobstore_->info_.inode_count;
info->used_nodes = blobstore_->info_.alloc_inode_count;
memcpy(info->name, kFsName, strlen(kFsName));
*out_actual = sizeof(vfs_query_info_t) + strlen(kFsName);
return ZX_OK;
}
case IOCTL_VFS_UNMOUNT_FS: {
zx_status_t status = Sync();
if (status != ZX_OK) {
FS_TRACE_ERROR("blobstore unmount failed to sync; unmounting anyway: %d\n", status);
}
*out_actual = 0;
return blobstore_->Unmount();
}
#ifdef __Fuchsia__
case IOCTL_VFS_GET_DEVICE_PATH: {
ssize_t len = ioctl_device_get_topo_path(blobstore_->blockfd_, 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) {
if (IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
return SpaceAllocate(len);
}
zx_status_t VnodeBlob::Unlink(fbl::StringPiece name, bool 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 = blobstore_->LookupBlob(digest, &out)) < 0) {
return status;
}
out->QueueUnlink();
return ZX_OK;
}
zx_status_t VnodeBlob::Mmap(int flags, size_t len, size_t* off, zx_handle_t* out) {
if (IsDirectory()) {
return ZX_ERR_NOT_SUPPORTED;
}
if (flags & FDIO_MMAP_FLAG_WRITE) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_rights_t rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_MAP;
rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0;
rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0;
return CopyVmo(rights, out);
}
zx_status_t VnodeBlob::Sync() {
// TODO(smklein): For now, this is a no-op, but it will change
// once the kBlobFlagSync flag is in use.
return ZX_OK;
}
} // namespace blobstore