// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dnode.h" namespace memfs { // Artificially cap the maximum in-memory file size to 512MB. constexpr size_t kMemfsMaxFileSize = 512 * 1024 * 1024; VnodeFile::VnodeFile(Vfs* vfs) : VnodeMemfs(vfs), vmo_size_(0), length_(0) {} VnodeFile::~VnodeFile() { vfs()->WillFreeVMO(vmo_size_); } zx_status_t VnodeFile::ValidateFlags(uint32_t flags) { if (flags & ZX_FS_FLAG_DIRECTORY) { return ZX_ERR_NOT_DIR; } return ZX_OK; } zx_status_t VnodeFile::Read(void* data, size_t len, size_t off, size_t* out_actual) { if ((off >= length_) || (!vmo_.is_valid())) { *out_actual = 0; return ZX_OK; } else if (len > length_ - off) { len = length_ - off; } zx_status_t status = vmo_.read(data, off, len); if (status == ZX_OK) { *out_actual = len; } return status; } zx_status_t VnodeFile::Write(const void* data, size_t len, size_t offset, size_t* out_actual) { zx_status_t status; size_t newlen = offset + len; newlen = newlen > kMemfsMaxFileSize ? kMemfsMaxFileSize : newlen; if ((status = vfs()->GrowVMO(vmo_, vmo_size_, newlen, &vmo_size_)) != ZX_OK) { return status; } // Accessing beyond the end of the file? Extend it. if (offset > length_) { // Zero-extending the tail of the file by writing to // an offset beyond the end of the file. ZeroTail(length_, offset); } size_t writelen = newlen - offset; if ((status = vmo_.write(data, offset, writelen)) != ZX_OK) { return status; } *out_actual = writelen; if (newlen > length_) { length_ = newlen; } if (writelen < len) { // short write because we're beyond the end of the permissible length return ZX_ERR_FILE_BIG; } UpdateModified(); return ZX_OK; } zx_status_t VnodeFile::Append(const void* data, size_t len, size_t* out_end, size_t* out_actual) { zx_status_t status = Write(data, len, length_, out_actual); *out_end = length_; return status; } zx_status_t VnodeFile::GetVmo(int flags, zx_handle_t* out) { zx_status_t status; if (!vmo_.is_valid()) { // First access to the file? Allocate it. if ((status = zx::vmo::create(0, 0, &vmo_)) != ZX_OK) { return status; } } // Let clients map and set the names of their VMOs. zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHTS_PROPERTY; rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0; rights |= (flags & FDIO_MMAP_FLAG_WRITE) ? ZX_RIGHT_WRITE : 0; rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0; zx::vmo out_vmo; if (flags & FDIO_MMAP_FLAG_PRIVATE) { if ((status = vmo_.clone(ZX_VMO_CLONE_COPY_ON_WRITE, 0, length_, &out_vmo)) != ZX_OK) { return status; } if ((status = out_vmo.replace(rights, &out_vmo)) != ZX_OK) { return status; } *out = out_vmo.release(); return ZX_OK; } if ((status = vmo_.duplicate(rights, &out_vmo)) != ZX_OK) { return status; } *out = out_vmo.release(); return ZX_OK; } zx_status_t VnodeFile::Getattr(vnattr_t* attr) { memset(attr, 0, sizeof(vnattr_t)); attr->inode = ino_; attr->mode = V_TYPE_FILE | V_IRUSR | V_IWUSR | V_IRGRP | V_IROTH; attr->size = length_; attr->blksize = kMemfsBlksize; attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE; attr->nlink = link_count_; attr->create_time = create_time_; attr->modify_time = modify_time_; return ZX_OK; } zx_status_t VnodeFile::GetHandles(uint32_t flags, zx_handle_t* hnd, uint32_t* type, zxrio_node_info_t* extra) { *type = fuchsia_io_NodeInfoTag_file; return ZX_OK; } zx_status_t VnodeFile::Truncate(size_t len) { zx_status_t status; if (len > kMemfsMaxFileSize) { return ZX_ERR_INVALID_ARGS; } if ((status = vfs()->GrowVMO(vmo_, vmo_size_, len, &vmo_size_)) != ZX_OK) { return status; } if (len < length_) { // Shrink the logical file length. // Zeroing the tail here is optional, but it saves memory. ZeroTail(len, length_); } else if (len > length_) { // Extend the logical file length. ZeroTail(length_, len); } length_ = len; UpdateModified(); return ZX_OK; } void VnodeFile::ZeroTail(size_t start, size_t end) { constexpr size_t kPageSize = static_cast(PAGE_SIZE); if (start % kPageSize != 0) { char buf[kPageSize]; size_t ppage_size = kPageSize - (start % kPageSize); memset(buf, 0, ppage_size); ZX_ASSERT(vmo_.write(buf, start, ppage_size) == ZX_OK); } end = fbl::min(fbl::round_up(end, kPageSize), vmo_size_); uint64_t decommit_offset = fbl::round_up(start, kPageSize); uint64_t decommit_length = end - decommit_offset; if (decommit_length > 0) { ZX_ASSERT(vmo_.op_range(ZX_VMO_OP_DECOMMIT, decommit_offset, decommit_length, nullptr, 0) == ZX_OK); } } } // namespace memfs