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