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 <fs/vmo-file.h>
6
7#include <limits.h>
8#include <string.h>
9
10#include <fbl/algorithm.h>
11#include <fbl/auto_lock.h>
12#include <fuchsia/io/c/fidl.h>
13#include <zircon/assert.h>
14#include <zircon/syscalls.h>
15
16namespace fs {
17namespace {
18constexpr uint64_t kVmoFileBlksize = PAGE_SIZE;
19
20zx_rights_t GetVmoRightsForAccessMode(uint32_t flags) {
21    zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP;
22    if (flags & ZX_FS_RIGHT_READABLE) {
23        rights |= ZX_RIGHT_READ;
24    }
25    if (flags & ZX_FS_RIGHT_WRITABLE) {
26        rights |= ZX_RIGHT_WRITE;
27    }
28    if ((flags & ZX_FS_RIGHT_READABLE) & !(flags & ZX_FS_RIGHT_WRITABLE)) {
29        rights |= ZX_RIGHT_EXECUTE;
30    }
31    return rights;
32}
33
34} // namespace
35
36VmoFile::VmoFile(const zx::vmo& unowned_vmo,
37                 size_t offset,
38                 size_t length,
39                 bool writable,
40                 VmoSharing vmo_sharing)
41    : vmo_handle_(unowned_vmo.get()),
42      offset_(offset), length_(length), writable_(writable), vmo_sharing_(vmo_sharing) {
43    ZX_DEBUG_ASSERT(vmo_handle_ != ZX_HANDLE_INVALID);
44}
45
46VmoFile::~VmoFile() {}
47
48zx_status_t VmoFile::ValidateFlags(uint32_t flags) {
49    if (flags & ZX_FS_FLAG_DIRECTORY) {
50        return ZX_ERR_NOT_DIR;
51    }
52    if (IsWritable(flags) && !writable_) {
53        return ZX_ERR_ACCESS_DENIED;
54    }
55    return ZX_OK;
56}
57
58zx_status_t VmoFile::Getattr(vnattr_t* attr) {
59    memset(attr, 0, sizeof(vnattr_t));
60    attr->mode = V_TYPE_FILE | V_IRUSR;
61    if (writable_) {
62        attr->mode |= V_IWUSR;
63    }
64    attr->size = length_;
65    attr->blksize = kVmoFileBlksize;
66    attr->blkcount = fbl::round_up(attr->size, kVmoFileBlksize) / VNATTR_BLKSIZE;
67    attr->nlink = 1;
68    return ZX_OK;
69}
70
71zx_status_t VmoFile::Read(void* data, size_t length, size_t offset, size_t* out_actual) {
72    if (length == 0u || offset >= length_) {
73        *out_actual = 0u;
74        return ZX_OK;
75    }
76
77    size_t remaining_length = length_ - offset;
78    if (length > remaining_length) {
79        length = remaining_length;
80    }
81    zx_status_t status = zx_vmo_read(vmo_handle_, data, offset_ + offset, length);
82    if (status != ZX_OK) {
83        return status;
84    }
85    *out_actual = length;
86    return ZX_OK;
87}
88
89zx_status_t VmoFile::Write(const void* data, size_t length, size_t offset, size_t* out_actual) {
90    ZX_DEBUG_ASSERT(writable_); // checked by the VFS
91
92    if (length == 0u) {
93        *out_actual = 0u;
94        return ZX_OK;
95    }
96    if (offset >= length_) {
97        return ZX_ERR_NO_SPACE;
98    }
99
100    size_t remaining_length = length_ - offset;
101    if (length > remaining_length) {
102        length = remaining_length;
103    }
104    zx_status_t status = zx_vmo_write(vmo_handle_, data, offset_ + offset, length);
105    if (status == ZX_OK) {
106        *out_actual = length;
107    }
108    return status;
109}
110
111zx_status_t VmoFile::GetHandles(uint32_t flags, zx_handle_t* hnd, uint32_t* type,
112                                zxrio_node_info_t* extra) {
113    ZX_DEBUG_ASSERT(!IsWritable(flags) || writable_); // checked by the VFS
114
115    zx::vmo vmo;
116    size_t offset;
117    zx_status_t status = AcquireVmo(GetVmoRightsForAccessMode(flags), &vmo, &offset);
118    if (status != ZX_OK) {
119        return status;
120    }
121
122    *hnd = vmo.release();
123    *type = fuchsia_io_NodeInfoTag_vmofile;
124    extra->vmofile.offset = offset;
125    extra->vmofile.length = length_;
126    return ZX_OK;
127}
128
129zx_status_t VmoFile::AcquireVmo(zx_rights_t rights, zx::vmo* out_vmo, size_t* out_offset) {
130    ZX_DEBUG_ASSERT(!(rights & ZX_RIGHT_WRITE) || writable_); // checked by the VFS
131
132    switch (vmo_sharing_) {
133    case VmoSharing::NONE:
134        return ZX_ERR_NOT_SUPPORTED;
135    case VmoSharing::DUPLICATE:
136        return DuplicateVmo(rights, out_vmo, out_offset);
137    case VmoSharing::CLONE_COW:
138        return CloneVmo(rights, out_vmo, out_offset);
139    }
140    __UNREACHABLE;
141}
142
143zx_status_t VmoFile::DuplicateVmo(zx_rights_t rights, zx::vmo* out_vmo, size_t* out_offset) {
144    zx_status_t status = zx_handle_duplicate(vmo_handle_, rights, out_vmo->reset_and_get_address());
145    if (status != ZX_OK)
146        return status;
147
148    *out_offset = offset_;
149    return ZX_OK;
150}
151
152zx_status_t VmoFile::CloneVmo(zx_rights_t rights, zx::vmo* out_vmo, size_t* out_offset) {
153    size_t clone_offset = fbl::round_down(offset_, static_cast<size_t>(PAGE_SIZE));
154    size_t clone_length = fbl::round_up(offset_ + length_, static_cast<size_t>(PAGE_SIZE)) -
155                          clone_offset;
156
157    if (!(rights & ZX_RIGHT_WRITE)) {
158        // Use a shared clone for read-only content.
159        // TODO(ZX-1154): Replace the mutex with fbl::call_once() once that's implemented.
160        // The shared clone is only initialized at most once so using a mutex is excessive.
161        fbl::AutoLock lock(&mutex_);
162        zx_status_t status;
163        if (!shared_clone_) {
164            status = zx_vmo_clone(vmo_handle_, ZX_VMO_CLONE_COPY_ON_WRITE,
165                                  clone_offset, clone_length,
166                                  shared_clone_.reset_and_get_address());
167            if (status != ZX_OK)
168                return status;
169        }
170
171        status = shared_clone_.duplicate(rights, out_vmo);
172        if (status != ZX_OK)
173            return status;
174    } else {
175        // Use separate clone for each client with writable COW access.
176        zx::vmo private_clone;
177        zx_status_t status = zx_vmo_clone(vmo_handle_, ZX_VMO_CLONE_COPY_ON_WRITE,
178                                          clone_offset, clone_length,
179                                          private_clone.reset_and_get_address());
180        if (status != ZX_OK)
181            return status;
182
183        status = private_clone.replace(rights, out_vmo);
184        if (status != ZX_OK)
185            return status;
186    }
187
188    *out_offset = offset_ - clone_offset;
189    return ZX_OK;
190}
191
192} // namespace fs
193