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 <assert.h>
6#include <ctype.h>
7#include <fcntl.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/stat.h>
12#include <unistd.h>
13
14#include <digest/digest.h>
15#include <zircon/device/device.h>
16#include <zircon/device/vfs.h>
17
18#include <fbl/ref_ptr.h>
19#include <fbl/string_piece.h>
20#include <lib/fdio/debug.h>
21#include <lib/fdio/vfs.h>
22#include <lib/sync/completion.h>
23#include <zircon/syscalls.h>
24
25#define ZXDEBUG 0
26
27#include <blobfs/blobfs.h>
28
29using digest::Digest;
30
31namespace blobfs {
32
33void VnodeBlob::fbl_recycle() {
34    if (GetState() != kBlobStatePurged && !IsDirectory()) {
35        // Relocate blobs which haven't been deleted to the closed cache.
36        blobfs_->VnodeReleaseSoft(this);
37    } else {
38        // Destroy blobs which have been purged.
39        delete this;
40    }
41}
42
43void VnodeBlob::TearDown() {
44    ZX_ASSERT(clone_watcher_.object() == ZX_HANDLE_INVALID);
45    if (blob_ != nullptr) {
46        blobfs_->DetachVmo(vmoid_);
47    }
48    blob_ = nullptr;
49}
50
51VnodeBlob::~VnodeBlob() {
52    TearDown();
53}
54
55zx_status_t VnodeBlob::ValidateFlags(uint32_t flags) {
56    if ((flags & ZX_FS_FLAG_DIRECTORY) && !IsDirectory()) {
57        return ZX_ERR_NOT_DIR;
58    }
59
60    if (flags & ZX_FS_RIGHT_WRITABLE) {
61        if (IsDirectory()) {
62            return ZX_ERR_NOT_FILE;
63        } else if (GetState() != kBlobStateEmpty) {
64            return ZX_ERR_ACCESS_DENIED;
65        }
66    }
67    return ZX_OK;
68}
69
70zx_status_t VnodeBlob::Readdir(fs::vdircookie_t* cookie, void* dirents, size_t len,
71                               size_t* out_actual) {
72    if (!IsDirectory()) {
73        return ZX_ERR_NOT_DIR;
74    }
75
76    return blobfs_->Readdir(cookie, dirents, len, out_actual);
77}
78
79zx_status_t VnodeBlob::Read(void* data, size_t len, size_t off, size_t* out_actual) {
80    TRACE_DURATION("blobfs", "VnodeBlob::Read", "len", len, "off", off);
81
82    if (IsDirectory()) {
83        return ZX_ERR_NOT_FILE;
84    }
85
86    return ReadInternal(data, len, off, out_actual);
87}
88
89zx_status_t VnodeBlob::Write(const void* data, size_t len, size_t offset,
90                             size_t* out_actual) {
91    TRACE_DURATION("blobfs", "VnodeBlob::Write", "len", len, "off", offset);
92    if (IsDirectory()) {
93        return ZX_ERR_NOT_FILE;
94    }
95    return WriteInternal(data, len, out_actual);
96}
97
98zx_status_t VnodeBlob::Append(const void* data, size_t len, size_t* out_end,
99                              size_t* out_actual) {
100    zx_status_t status = WriteInternal(data, len, out_actual);
101    if (GetState() == kBlobStateDataWrite) {
102        ZX_DEBUG_ASSERT(write_info_ != nullptr);
103        *out_actual = write_info_->bytes_written;
104    } else {
105        *out_actual = inode_.blob_size;
106    }
107    return status;
108}
109
110zx_status_t VnodeBlob::Lookup(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name) {
111    TRACE_DURATION("blobfs", "VnodeBlob::Lookup", "name", name);
112    assert(memchr(name.data(), '/', name.length()) == nullptr);
113    if (name == "." && IsDirectory()) {
114        // Special case: Accessing root directory via '.'
115        *out = fbl::RefPtr<VnodeBlob>(this);
116        return ZX_OK;
117    }
118
119    if (!IsDirectory()) {
120        return ZX_ERR_NOT_SUPPORTED;
121    }
122
123    zx_status_t status;
124    Digest digest;
125    if ((status = digest.Parse(name.data(), name.length())) != ZX_OK) {
126        return status;
127    }
128    fbl::RefPtr<VnodeBlob> vn;
129    if ((status = blobfs_->LookupBlob(digest, &vn)) < 0) {
130        return status;
131    }
132    *out = fbl::move(vn);
133    return ZX_OK;
134}
135
136zx_status_t VnodeBlob::Getattr(vnattr_t* a) {
137    memset(a, 0, sizeof(vnattr_t));
138    a->mode = (IsDirectory() ? V_TYPE_DIR : V_TYPE_FILE) | V_IRUSR;
139    a->inode = fuchsia_io_INO_UNKNOWN;
140    a->size = IsDirectory() ? 0 : SizeData();
141    a->blksize = kBlobfsBlockSize;
142    a->blkcount = inode_.num_blocks * (kBlobfsBlockSize / VNATTR_BLKSIZE);
143    a->nlink = 1;
144    a->create_time = 0;
145    a->modify_time = 0;
146    return ZX_OK;
147}
148
149zx_status_t VnodeBlob::Create(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name, uint32_t mode) {
150    TRACE_DURATION("blobfs", "VnodeBlob::Create", "name", name, "mode", mode);
151    assert(memchr(name.data(), '/', name.length()) == nullptr);
152
153    if (!IsDirectory()) {
154        return ZX_ERR_NOT_SUPPORTED;
155    }
156
157    Digest digest;
158    zx_status_t status;
159    if ((status = digest.Parse(name.data(), name.length())) != ZX_OK) {
160        return status;
161    }
162    fbl::RefPtr<VnodeBlob> vn;
163    if ((status = blobfs_->NewBlob(digest, &vn)) != ZX_OK) {
164        return status;
165    }
166    vn->fd_count_ = 1;
167    *out = fbl::move(vn);
168    return ZX_OK;
169}
170
171zx_status_t VnodeBlob::Truncate(size_t len) {
172    TRACE_DURATION("blobfs", "VnodeBlob::Truncate", "len", len);
173
174    if (IsDirectory()) {
175        return ZX_ERR_NOT_SUPPORTED;
176    }
177
178    return SpaceAllocate(len);
179}
180
181#ifdef __Fuchsia__
182
183constexpr const char kFsName[] = "blobfs";
184
185zx_status_t VnodeBlob::QueryFilesystem(fuchsia_io_FilesystemInfo* info) {
186    static_assert(fbl::constexpr_strlen(kFsName) + 1 < fuchsia_io_MAX_FS_NAME_BUFFER,
187                  "Blobfs name too long");
188
189    memset(info, 0, sizeof(*info));
190    info->block_size = kBlobfsBlockSize;
191    info->max_filename_size = Digest::kLength * 2;
192    info->fs_type = VFS_TYPE_BLOBFS;
193    info->fs_id = blobfs_->GetFsId();
194    info->total_bytes = blobfs_->info_.block_count * blobfs_->info_.block_size;
195    info->used_bytes = blobfs_->info_.alloc_block_count * blobfs_->info_.block_size;
196    info->total_nodes = blobfs_->info_.inode_count;
197    info->used_nodes = blobfs_->info_.alloc_inode_count;
198    strlcpy(reinterpret_cast<char*>(info->name), kFsName, fuchsia_io_MAX_FS_NAME_BUFFER);
199    return ZX_OK;
200}
201
202zx_status_t VnodeBlob::GetDevicePath(size_t buffer_len, char* out_name, size_t* out_len) {
203    ssize_t len = ioctl_device_get_topo_path(blobfs_->Fd(), out_name, buffer_len);
204    if (len < 0) {
205        return static_cast<zx_status_t>(len);
206    }
207    *out_len = len;
208    return ZX_OK;
209}
210#endif
211
212zx_status_t VnodeBlob::Unlink(fbl::StringPiece name, bool must_be_dir) {
213    TRACE_DURATION("blobfs", "VnodeBlob::Unlink", "name", name, "must_be_dir", must_be_dir);
214    assert(memchr(name.data(), '/', name.length()) == nullptr);
215
216    if (!IsDirectory()) {
217        return ZX_ERR_NOT_SUPPORTED;
218    }
219
220    zx_status_t status;
221    Digest digest;
222    fbl::RefPtr<VnodeBlob> out;
223    if ((status = digest.Parse(name.data(), name.length())) != ZX_OK) {
224        return status;
225    } else if ((status = blobfs_->LookupBlob(digest, &out)) < 0) {
226        return status;
227    }
228    out->QueueUnlink();
229    return ZX_OK;
230}
231
232zx_status_t VnodeBlob::GetVmo(int flags, zx_handle_t* out) {
233    TRACE_DURATION("blobfs", "VnodeBlob::GetVmo", "flags", flags);
234
235    if (IsDirectory()) {
236        return ZX_ERR_NOT_SUPPORTED;
237    }
238    if (flags & FDIO_MMAP_FLAG_WRITE) {
239        return ZX_ERR_NOT_SUPPORTED;
240    } else if (flags & FDIO_MMAP_FLAG_EXACT) {
241        return ZX_ERR_NOT_SUPPORTED;
242    }
243
244    // Let clients map and set the names of their VMOs.
245    zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHTS_PROPERTY;
246    // We can ignore FDIO_MMAP_FLAG_PRIVATE, since private / shared access
247    // to the underlying VMO can both be satisfied with a clone due to
248    // the immutability of blobfs blobs.
249    rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0;
250    rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0;
251    return CloneVmo(rights, out);
252}
253
254void VnodeBlob::Sync(SyncCallback closure) {
255    if (atomic_load(&syncing_)) {
256        blobfs_->Sync([this, cb = fbl::move(closure)](zx_status_t status) {
257            if (status != ZX_OK) {
258                cb(status);
259                return;
260            }
261
262            fs::WriteTxn sync_txn(blobfs_);
263            sync_txn.EnqueueFlush();
264            status = sync_txn.Transact();
265            cb(status);
266        });
267    } else {
268        closure(ZX_OK);
269    }
270}
271
272void VnodeBlob::CompleteSync() {
273    fsync(blobfs_->Fd());
274    atomic_store(&syncing_, false);
275}
276
277fbl::RefPtr<VnodeBlob> VnodeBlob::CloneWatcherTeardown() {
278    if (clone_watcher_.is_pending()) {
279        clone_watcher_.Cancel();
280        clone_watcher_.set_object(ZX_HANDLE_INVALID);
281        return fbl::move(clone_ref_);
282    }
283    return nullptr;
284}
285
286zx_status_t VnodeBlob::Open(uint32_t flags, fbl::RefPtr<Vnode>* out_redirect) {
287    fd_count_++;
288    return ZX_OK;
289}
290
291zx_status_t VnodeBlob::Close() {
292    ZX_DEBUG_ASSERT_MSG(fd_count_ > 0, "Closing blob with no fds open");
293    fd_count_--;
294    // Attempt purge in case blob was unlinked prior to close
295    TryPurge();
296    return ZX_OK;
297}
298
299void VnodeBlob::Purge() {
300    ZX_DEBUG_ASSERT(fd_count_ == 0);
301    ZX_DEBUG_ASSERT(Purgeable());
302    blobfs_->PurgeBlob(this);
303    SetState(kBlobStatePurged);
304}
305
306} // namespace blobfs
307