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