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