blob: faaa7e7e4fdb6be38bae5aa8c809194f9eeb7029 [file] [log] [blame] [edit]
// 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 <fuchsia/io/llcpp/fidl.h>
#include <limits.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fs/vfs.h>
#include <fs/vfs_types.h>
#include <fs/vmo_file.h>
namespace fio = ::llcpp::fuchsia::io;
namespace fs {
namespace {
constexpr uint64_t kVmoFileBlksize = PAGE_SIZE;
zx_rights_t GetVmoRightsForAccessMode(fs::Rights fs_rights) {
zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP;
if (fs_rights.read) {
rights |= ZX_RIGHT_READ;
}
if (fs_rights.write) {
rights |= ZX_RIGHT_WRITE;
}
// TODO(mdempsky): Add ZX_FS_RIGHT_EXECUTABLE flag?
return rights;
}
} // namespace
VmoFile::VmoFile(const zx::vmo& unowned_vmo, size_t offset, size_t length, bool writable,
VmoSharing vmo_sharing)
: vmo_handle_(unowned_vmo.get()),
offset_(offset),
length_(length),
writable_(writable),
vmo_sharing_(vmo_sharing) {
ZX_DEBUG_ASSERT(vmo_handle_ != ZX_HANDLE_INVALID);
}
VmoFile::~VmoFile() {}
VnodeProtocolSet VmoFile::GetProtocols() const { return VnodeProtocol::kMemory; }
bool VmoFile::ValidateRights(Rights rights) { return !rights.write || writable_; }
zx_status_t VmoFile::GetAttributes(VnodeAttributes* attr) {
*attr = VnodeAttributes();
attr->mode = V_TYPE_FILE | V_IRUSR;
if (writable_) {
attr->mode |= V_IWUSR;
}
attr->inode = fio::INO_UNKNOWN;
attr->content_size = length_;
attr->storage_size = fbl::round_up(attr->content_size, kVmoFileBlksize);
attr->link_count = 1;
return ZX_OK;
}
zx_status_t VmoFile::Read(void* data, size_t length, size_t offset, size_t* out_actual) {
if (length == 0u || offset >= length_) {
*out_actual = 0u;
return ZX_OK;
}
size_t remaining_length = length_ - offset;
if (length > remaining_length) {
length = remaining_length;
}
zx_status_t status = zx_vmo_read(vmo_handle_, data, offset_ + offset, length);
if (status != ZX_OK) {
return status;
}
*out_actual = length;
return ZX_OK;
}
zx_status_t VmoFile::Write(const void* data, size_t length, size_t offset, size_t* out_actual) {
ZX_DEBUG_ASSERT(writable_); // checked by the VFS
if (length == 0u) {
*out_actual = 0u;
return ZX_OK;
}
if (offset >= length_) {
return ZX_ERR_NO_SPACE;
}
size_t remaining_length = length_ - offset;
if (length > remaining_length) {
length = remaining_length;
}
zx_status_t status = zx_vmo_write(vmo_handle_, data, offset_ + offset, length);
if (status == ZX_OK) {
*out_actual = length;
}
return status;
}
zx_status_t VmoFile::GetNodeInfoForProtocol([[maybe_unused]] VnodeProtocol protocol, Rights rights,
VnodeRepresentation* info) {
ZX_DEBUG_ASSERT(!rights.write || writable_); // checked by the VFS
zx::vmo vmo;
size_t offset;
zx_status_t status = AcquireVmo(GetVmoRightsForAccessMode(rights), &vmo, &offset);
if (status != ZX_OK) {
return status;
}
*info =
fs::VnodeRepresentation::Memory{.vmo = std::move(vmo), .offset = offset, .length = length_};
return ZX_OK;
}
zx_status_t VmoFile::AcquireVmo(zx_rights_t rights, zx::vmo* out_vmo, size_t* out_offset) {
ZX_DEBUG_ASSERT(!(rights & ZX_RIGHT_WRITE) || writable_); // checked by the VFS
switch (vmo_sharing_) {
case VmoSharing::NONE:
return ZX_ERR_NOT_SUPPORTED;
case VmoSharing::DUPLICATE:
return DuplicateVmo(rights, out_vmo, out_offset);
case VmoSharing::CLONE_COW:
return CloneVmo(rights, out_vmo, out_offset);
}
__UNREACHABLE;
}
zx_status_t VmoFile::DuplicateVmo(zx_rights_t rights, zx::vmo* out_vmo, size_t* out_offset) {
zx_status_t status = zx_handle_duplicate(vmo_handle_, rights, out_vmo->reset_and_get_address());
if (status != ZX_OK)
return status;
*out_offset = offset_;
return ZX_OK;
}
zx_status_t VmoFile::CloneVmo(zx_rights_t rights, zx::vmo* out_vmo, size_t* out_offset) {
size_t clone_offset = fbl::round_down(offset_, static_cast<size_t>(PAGE_SIZE));
size_t clone_length =
fbl::round_up(offset_ + length_, static_cast<size_t>(PAGE_SIZE)) - clone_offset;
if (!(rights & ZX_RIGHT_WRITE)) {
// Use a shared clone for read-only content.
zx_status_t status = ZX_OK;
std::call_once(shared_clone_.once, [&]() {
status = zx_vmo_create_child(vmo_handle_, ZX_VMO_CHILD_COPY_ON_WRITE, clone_offset,
clone_length, shared_clone_.vmo.reset_and_get_address());
});
if (status != ZX_OK)
return status;
status = shared_clone_.vmo.duplicate(rights, out_vmo);
if (status != ZX_OK)
return status;
} else {
// Use separate clone for each client with writable COW access.
zx::vmo private_clone;
zx_status_t status = zx_vmo_create_child(vmo_handle_, ZX_VMO_CHILD_COPY_ON_WRITE, clone_offset,
clone_length, private_clone.reset_and_get_address());
if (status != ZX_OK)
return status;
status = private_clone.replace(rights, out_vmo);
if (status != ZX_OK)
return status;
}
*out_offset = offset_ - clone_offset;
return ZX_OK;
}
} // namespace fs