1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <inttypes.h> 6#include <fcntl.h> 7#include <limits.h> 8#include <stdlib.h> 9#include <string.h> 10#include <sys/stat.h> 11 12#include <fbl/algorithm.h> 13#include <fbl/alloc_checker.h> 14#include <fbl/atomic.h> 15#include <fbl/ref_ptr.h> 16#include <fbl/unique_ptr.h> 17#include <lib/fdio/vfs.h> 18#include <fs/vfs.h> 19#include <lib/memfs/cpp/vnode.h> 20#include <zircon/device/vfs.h> 21 22#include "dnode.h" 23 24namespace memfs { 25 26// Artificially cap the maximum in-memory file size to 512MB. 27constexpr size_t kMemfsMaxFileSize = 512 * 1024 * 1024; 28 29VnodeFile::VnodeFile(Vfs* vfs) 30 : VnodeMemfs(vfs), vmo_size_(0), length_(0) {} 31 32VnodeFile::~VnodeFile() { 33 vfs()->WillFreeVMO(vmo_size_); 34} 35 36zx_status_t VnodeFile::ValidateFlags(uint32_t flags) { 37 if (flags & ZX_FS_FLAG_DIRECTORY) { 38 return ZX_ERR_NOT_DIR; 39 } 40 return ZX_OK; 41} 42 43zx_status_t VnodeFile::Read(void* data, size_t len, size_t off, size_t* out_actual) { 44 if ((off >= length_) || (!vmo_.is_valid())) { 45 *out_actual = 0; 46 return ZX_OK; 47 } else if (len > length_ - off) { 48 len = length_ - off; 49 } 50 51 zx_status_t status = vmo_.read(data, off, len); 52 if (status == ZX_OK) { 53 *out_actual = len; 54 } 55 return status; 56} 57 58zx_status_t VnodeFile::Write(const void* data, size_t len, size_t offset, 59 size_t* out_actual) { 60 zx_status_t status; 61 size_t newlen = offset + len; 62 newlen = newlen > kMemfsMaxFileSize ? kMemfsMaxFileSize : newlen; 63 if ((status = vfs()->GrowVMO(vmo_, vmo_size_, newlen, &vmo_size_)) != ZX_OK) { 64 return status; 65 } 66 // Accessing beyond the end of the file? Extend it. 67 if (offset > length_) { 68 // Zero-extending the tail of the file by writing to 69 // an offset beyond the end of the file. 70 ZeroTail(length_, offset); 71 } 72 size_t writelen = newlen - offset; 73 if ((status = vmo_.write(data, offset, writelen)) != ZX_OK) { 74 return status; 75 } 76 *out_actual = writelen; 77 78 if (newlen > length_) { 79 length_ = newlen; 80 } 81 if (writelen < len) { 82 // short write because we're beyond the end of the permissible length 83 return ZX_ERR_FILE_BIG; 84 } 85 UpdateModified(); 86 return ZX_OK; 87} 88 89zx_status_t VnodeFile::Append(const void* data, size_t len, size_t* out_end, 90 size_t* out_actual) { 91 zx_status_t status = Write(data, len, length_, out_actual); 92 *out_end = length_; 93 return status; 94} 95 96zx_status_t VnodeFile::GetVmo(int flags, zx_handle_t* out) { 97 zx_status_t status; 98 if (!vmo_.is_valid()) { 99 // First access to the file? Allocate it. 100 if ((status = zx::vmo::create(0, 0, &vmo_)) != ZX_OK) { 101 return status; 102 } 103 } 104 105 // Let clients map and set the names of their VMOs. 106 zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHTS_PROPERTY; 107 rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0; 108 rights |= (flags & FDIO_MMAP_FLAG_WRITE) ? ZX_RIGHT_WRITE : 0; 109 rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0; 110 zx::vmo out_vmo; 111 if (flags & FDIO_MMAP_FLAG_PRIVATE) { 112 if ((status = vmo_.clone(ZX_VMO_CLONE_COPY_ON_WRITE, 0, length_, 113 &out_vmo)) != ZX_OK) { 114 return status; 115 } 116 117 if ((status = out_vmo.replace(rights, &out_vmo)) != ZX_OK) { 118 return status; 119 } 120 *out = out_vmo.release(); 121 return ZX_OK; 122 } 123 124 if ((status = vmo_.duplicate(rights, &out_vmo)) != ZX_OK) { 125 return status; 126 } 127 *out = out_vmo.release(); 128 return ZX_OK; 129} 130 131zx_status_t VnodeFile::Getattr(vnattr_t* attr) { 132 memset(attr, 0, sizeof(vnattr_t)); 133 attr->inode = ino_; 134 attr->mode = V_TYPE_FILE | V_IRUSR | V_IWUSR | V_IRGRP | V_IROTH; 135 attr->size = length_; 136 attr->blksize = kMemfsBlksize; 137 attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE; 138 attr->nlink = link_count_; 139 attr->create_time = create_time_; 140 attr->modify_time = modify_time_; 141 return ZX_OK; 142} 143 144zx_status_t VnodeFile::GetHandles(uint32_t flags, zx_handle_t* hnd, uint32_t* type, 145 zxrio_node_info_t* extra) { 146 *type = fuchsia_io_NodeInfoTag_file; 147 return ZX_OK; 148} 149 150zx_status_t VnodeFile::Truncate(size_t len) { 151 zx_status_t status; 152 if (len > kMemfsMaxFileSize) { 153 return ZX_ERR_INVALID_ARGS; 154 } 155 if ((status = vfs()->GrowVMO(vmo_, vmo_size_, len, &vmo_size_)) != ZX_OK) { 156 return status; 157 } 158 if (len < length_) { 159 // Shrink the logical file length. 160 // Zeroing the tail here is optional, but it saves memory. 161 ZeroTail(len, length_); 162 } else if (len > length_) { 163 // Extend the logical file length. 164 ZeroTail(length_, len); 165 } 166 167 length_ = len; 168 UpdateModified(); 169 return ZX_OK; 170} 171 172void VnodeFile::ZeroTail(size_t start, size_t end) { 173 constexpr size_t kPageSize = static_cast<size_t>(PAGE_SIZE); 174 if (start % kPageSize != 0) { 175 char buf[kPageSize]; 176 size_t ppage_size = kPageSize - (start % kPageSize); 177 memset(buf, 0, ppage_size); 178 ZX_ASSERT(vmo_.write(buf, start, ppage_size) == ZX_OK); 179 } 180 end = fbl::min(fbl::round_up(end, kPageSize), vmo_size_); 181 uint64_t decommit_offset = fbl::round_up(start, kPageSize); 182 uint64_t decommit_length = end - decommit_offset; 183 184 if (decommit_length > 0) { 185 ZX_ASSERT(vmo_.op_range(ZX_VMO_OP_DECOMMIT, decommit_offset, 186 decommit_length, nullptr, 0) == ZX_OK); 187 } 188} 189 190} // namespace memfs 191