| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <zircon/time.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string_view> |
| |
| #include <fbl/algorithm.h> |
| #include <safemath/checked_math.h> |
| |
| #include "src/storage/lib/vfs/cpp/vfs_types.h" |
| |
| #ifdef __Fuchsia__ |
| #include <zircon/syscalls.h> |
| |
| #include <utility> |
| |
| #include <fbl/auto_lock.h> |
| #endif |
| |
| #include "src/storage/minfs/directory.h" |
| #include "src/storage/minfs/file.h" |
| #include "src/storage/minfs/minfs_private.h" |
| #include "src/storage/minfs/vnode.h" |
| |
| namespace minfs { |
| |
| void VnodeMinfs::SetIno(ino_t ino) { |
| ZX_DEBUG_ASSERT(ino_ == 0); |
| ino_ = ino; |
| } |
| |
| void VnodeMinfs::AddLink() { |
| ZX_ASSERT_MSG(!add_overflow(inode_.link_count, 1, &inode_.link_count), "Exceeded max link count"); |
| } |
| |
| void VnodeMinfs::InodeSync(PendingWork* transaction, uint32_t flags) { |
| // by default, c/mtimes are not updated to current time |
| if (flags != kMxFsSyncDefault) { |
| zx_time_t cur_time = GetTimeUTC(); |
| // update times before syncing |
| if ((flags & kMxFsSyncMtime) != 0) { |
| inode_.modify_time = cur_time; |
| } |
| if ((flags & kMxFsSyncCtime) != 0) { |
| inode_.create_time = cur_time; |
| } |
| } |
| |
| fs_->InodeUpdate(transaction, ino_, &inode_); |
| } |
| |
| // Delete all blocks (relative to a file) from "start" (inclusive) to the end of |
| // the file. Does not update mtime/atime. |
| zx::result<> VnodeMinfs::BlocksShrink(PendingWork* transaction, blk_t start) { |
| VnodeMapper mapper(this); |
| VnodeIterator iterator; |
| if (auto status = iterator.Init(&mapper, transaction, start); status.is_error()) |
| return status; |
| uint64_t block_count = VnodeMapper::kMaxBlocks - start; |
| while (block_count > 0) { |
| uint64_t count; |
| if (iterator.Blk() == 0) { |
| count = iterator.GetContiguousBlockCount(block_count); |
| } else { |
| count = 1; |
| DeleteBlock(transaction, static_cast<blk_t>(iterator.file_block()), iterator.Blk(), |
| /*indirect=*/false); |
| if (auto status = iterator.SetBlk(0); status.is_error()) |
| return status; |
| } |
| if (auto status = iterator.Advance(count); status.is_error()) |
| return status; |
| block_count -= count; |
| } |
| if (auto status = iterator.Flush(); status.is_error()) |
| return status; |
| // Shrink the buffer backing the virtual indirect file. |
| if (indirect_file_) { |
| uint64_t indirect_block_pointers; |
| if (start <= VnodeMapper::kIndirectFileStartBlock) { |
| indirect_block_pointers = 0; |
| } else if (start <= VnodeMapper::kDoubleIndirectFileStartBlock) { |
| indirect_block_pointers = start - VnodeMapper::kIndirectFileStartBlock; |
| } else { |
| indirect_block_pointers = |
| (start - VnodeMapper::kDoubleIndirectFileStartBlock) + |
| static_cast<uint64_t>(kMinfsIndirect + kMinfsDoublyIndirect) * kMinfsDirectPerIndirect; |
| } |
| indirect_file_->Shrink( |
| fbl::round_up(indirect_block_pointers * sizeof(blk_t), fs_->BlockSize()) / |
| fs_->BlockSize()); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<LazyBuffer*> VnodeMinfs::GetIndirectFile() { |
| if (!indirect_file_) { |
| zx::result<std::unique_ptr<LazyBuffer>> buffer = LazyBuffer::Create( |
| fs_->bc_.get(), "minfs-indirect-file", static_cast<uint32_t>(fs_->BlockSize())); |
| if (buffer.is_error()) |
| return buffer.take_error(); |
| indirect_file_ = std::move(buffer).value(); |
| } |
| return zx::ok(indirect_file_.get()); |
| } |
| |
| #ifdef __Fuchsia__ |
| |
| // TODO(smklein): Even this hack can be optimized; a bitmap could be used to |
| // track all 'empty/read/dirty' blocks for each vnode, rather than reading |
| // the entire file. |
| zx::result<> VnodeMinfs::InitVmo() { |
| TRACE_DURATION("minfs", "VnodeMinfs::InitVmo"); |
| if (vmo_.is_valid()) { |
| return zx::ok(); |
| } |
| |
| const size_t vmo_size = fbl::round_up(GetSize(), fs_->BlockSize()); |
| if (zx_status_t status = zx::vmo::create(vmo_size, ZX_VMO_RESIZABLE, &vmo_); status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to initialize vmo; error: " << status; |
| return zx::error(status); |
| } |
| vmo_size_ = vmo_size; |
| |
| zx_object_set_property(vmo_.get(), ZX_PROP_NAME, "minfs-inode", 11); |
| |
| if (zx_status_t status = fs_->bc_->BlockAttachVmo(vmo_, &vmoid_); status != ZX_OK) { |
| vmo_.reset(); |
| return zx::error(status); |
| } |
| |
| fs::BufferedOperationsBuilder builder; |
| VnodeMapper mapper(this); |
| VnodeIterator iterator; |
| if (auto status = iterator.Init(&mapper, nullptr, 0); status.is_error()) |
| return status.take_error(); |
| uint64_t block_count = vmo_size / fs_->BlockSize(); |
| while (block_count > 0) { |
| blk_t block = iterator.Blk(); |
| uint64_t count = iterator.GetContiguousBlockCount(block_count); |
| if (block) { |
| fs_->ValidateBno(block); |
| fs::internal::BorrowedBuffer buffer(vmoid_.get()); |
| builder.Add(storage::Operation{.type = storage::OperationType::kRead, |
| .vmo_offset = iterator.file_block(), |
| .dev_offset = block + fs_->Info().dat_block, |
| .length = count}, |
| &buffer); |
| } |
| if (auto status = iterator.Advance(count); status.is_error()) |
| return status.take_error(); |
| block_count -= count; |
| } |
| |
| zx_status_t status = fs_->GetMutableBcache()->RunRequests(builder.TakeOperations()); |
| return zx::make_result(status); |
| } |
| |
| #endif |
| |
| void VnodeMinfs::AllocateIndirect(PendingWork* transaction, blk_t* block) { |
| ZX_DEBUG_ASSERT(transaction != nullptr); |
| fs_->BlockNew(transaction, block); |
| inode_.block_count++; |
| } |
| |
| zx::result<blk_t> VnodeMinfs::BlockGetWritable(Transaction* transaction, blk_t n) { |
| VnodeMapper mapper(this); |
| VnodeIterator iterator; |
| if (auto status = iterator.Init(&mapper, transaction, n); status.is_error()) |
| return status.take_error(); |
| blk_t block = iterator.Blk(); |
| AcquireWritableBlock(transaction, n, block, &block); |
| if (block != iterator.Blk()) { |
| if (auto status = iterator.SetBlk(block); status.is_error()) |
| return status.take_error(); |
| } |
| |
| if (auto status = iterator.Flush(); status.is_error()) { |
| return status.take_error(); |
| } |
| |
| return zx::ok(block); |
| } |
| |
| zx::result<blk_t> VnodeMinfs::BlockGetReadable(blk_t n) { |
| VnodeMapper mapper(this); |
| zx::result<std::pair<blk_t, uint64_t>> mapping = mapper.MapToBlk(BlockRange(n, n + 1)); |
| if (mapping.is_error()) |
| return mapping.take_error(); |
| |
| return zx::ok(mapping.value().first); |
| } |
| |
| zx::result<> VnodeMinfs::ReadExactInternal(PendingWork* transaction, void* data, size_t len, |
| size_t off) { |
| size_t actual; |
| auto status = ReadInternal(transaction, data, len, off, &actual); |
| if (status.is_error()) { |
| return status; |
| } |
| if (actual != len) { |
| return zx::error(ZX_ERR_IO); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> VnodeMinfs::WriteExactInternal(Transaction* transaction, const void* data, size_t len, |
| size_t off) { |
| size_t actual; |
| auto status = WriteInternal(transaction, static_cast<const uint8_t*>(data), len, off, &actual); |
| if (status.is_error()) { |
| return status; |
| } |
| if (actual != len) { |
| return zx::error(ZX_ERR_IO); |
| } |
| InodeSync(transaction, kMxFsSyncMtime); |
| return zx::ok(); |
| } |
| |
| zx::result<> VnodeMinfs::RemoveInodeLink(Transaction* transaction) { |
| ZX_ASSERT(inode_.link_count > 0); |
| |
| // This effectively 'unlinks' the target node without deleting the direntry |
| inode_.link_count--; |
| if (IsDirectory()) { |
| if (inode_.link_count == 1) { |
| // Directories are initialized with two links, since they point |
| // to themselves via ".". Thus, when they reach "one link", they |
| // are only pointed to by themselves, and should be deleted. |
| inode_.link_count--; |
| } |
| } |
| |
| if (IsUnlinked()) { |
| // The open_count() needs to be read within the lock to make the compiler's checking happy, |
| // but we don't actually need this lock and can run into recursive locking if we hold it for |
| // the subsequent operations in this block. |
| size_t oc; |
| { |
| std::lock_guard lock(mutex_); |
| oc = open_count(); |
| } |
| |
| #ifdef __Fuchsia__ |
| // Regardless of whether there are still open connections, we need to notify potential watchers |
| // that this node is being deleted. |
| Notify(std::string_view("."), fuchsia_io::wire::WatchEvent::kDeleted); |
| #endif // __Fuchsia__ |
| |
| if (oc == 0) { |
| // No need to flush/retain dirty cache or the reservations for unlinked |
| // inode. |
| DropCachedWrites(); |
| if (auto status = Purge(transaction); status.is_error()) { |
| return status; |
| } |
| } else { |
| fs_->AddUnlinked(transaction, this); |
| if (IsDirectory()) { |
| // If it's a directory, we need to remove the . and .. entries, which should be the only |
| // entries. |
| inode_.dirent_count = 0; |
| if (auto status = TruncateInternal(transaction, 0); status.is_error()) { |
| return status; |
| } |
| } |
| } |
| } |
| |
| InodeSync(transaction, kMxFsSyncMtime); |
| return zx::ok(); |
| } |
| |
| void VnodeMinfs::RecycleNode() { |
| size_t count; |
| { |
| // Need to hold the lock to check open_count(), but be careful not to hold it across this class |
| // getting deleted at the bottom of this function. |
| std::lock_guard lock(mutex_); |
| count = open_count(); |
| } |
| ZX_DEBUG_ASSERT_MSG(count == 0, "open_count=%zu", count); |
| if (!IsUnlinked()) { |
| // If this node has not been purged already, remove it from the |
| // hash map. If it has been purged; it will already be absent |
| // from the map (and may have already been replaced with a new |
| // node, if the inode has been re-used). |
| fs_->VnodeRelease(this); |
| } |
| delete this; |
| } |
| |
| VnodeMinfs::~VnodeMinfs() { |
| #ifdef __Fuchsia__ |
| // Detach the vmoids from the underlying block device, |
| // so the underlying VMO may be released. |
| size_t request_count = 0; |
| block_fifo_request_t request[2]; |
| if (vmoid_.IsAttached()) { |
| request[request_count].vmoid = vmoid_.TakeId(); |
| request[request_count].command = {.opcode = BLOCK_OPCODE_CLOSE_VMO, .flags = 0}; |
| request_count++; |
| } |
| if (request_count) { |
| fs_->bc_->GetDevice()->FifoTransaction(request, request_count); |
| } |
| #endif |
| if (indirect_file_) { |
| auto status = indirect_file_->Detach(fs_->bc_.get()); |
| ZX_DEBUG_ASSERT(status.is_ok()); |
| } |
| } |
| |
| zx::result<> VnodeMinfs::Purge(Transaction* transaction) { |
| { |
| std::lock_guard lock(mutex_); |
| ZX_DEBUG_ASSERT(open_count() == 0); |
| } |
| ZX_DEBUG_ASSERT(IsUnlinked()); |
| fs_->VnodeRelease(this); |
| return fs_->InoFree(transaction, this); |
| } |
| |
| zx::result<> VnodeMinfs::RemoveUnlinked() { |
| ZX_ASSERT(IsUnlinked()); |
| |
| auto transaction_or = fs_->BeginTransaction(0, 0); |
| if (transaction_or.is_error()) { |
| // In case of error, we still need to release this vnode because it's not possible to retry, |
| // and we cannot block destruction. The inode will get cleaned up on next remount. |
| fs_->VnodeRelease(this); |
| return transaction_or.take_error(); |
| } |
| // The transaction may go async in journal layer. Hold the reference over this |
| // vnode so that we keep the vnode around until the transaction is complete. |
| transaction_or->PinVnode(fbl::RefPtr(this)); |
| |
| fs_->RemoveUnlinked(transaction_or.value().get(), this); |
| if (auto status = Purge(transaction_or.value().get()); status.is_error()) { |
| return status; |
| } |
| |
| fs_->CommitTransaction(std::move(transaction_or.value())); |
| return zx::ok(); |
| } |
| |
| zx_status_t VnodeMinfs::CloseNode() { |
| { |
| std::lock_guard lock(mutex_); |
| if (open_count() != 0) { |
| return ZX_OK; |
| } |
| } |
| |
| if (!IsUnlinked()) { |
| auto result = FlushCachedWrites(); |
| if (result.is_error()) { |
| FX_LOGS(ERROR) << "Failed(" << result.error_value() |
| << ") to flush pending writes for inode:" << GetIno(); |
| } |
| return result.status_value(); |
| } |
| |
| // This vnode is unlinked and open_count() == 0. We don't need not flush the dirty |
| // contents of the vnode to disk. |
| DropCachedWrites(); |
| return RemoveUnlinked().status_value(); |
| } |
| |
| // Internal read. Usable on directories. |
| zx::result<> VnodeMinfs::ReadInternal(PendingWork* transaction, void* vdata, size_t len, size_t off, |
| size_t* actual) { |
| // clip to EOF |
| if (off >= GetSize()) { |
| *actual = 0; |
| return zx::ok(); |
| } |
| if (len > (GetSize() - off)) { |
| len = GetSize() - off; |
| } |
| |
| #ifdef __Fuchsia__ |
| if (auto status = InitVmo(); status.is_error()) { |
| return status.take_error(); |
| } else if (zx_status_t status = vmo_.read(vdata, off, len); status != ZX_OK) { |
| return zx::error(status); |
| } else { |
| *actual = len; |
| } |
| #else |
| uint8_t* data = static_cast<uint8_t*>(vdata); |
| uint8_t* start = data; |
| uint32_t n = static_cast<uint32_t>(off / fs_->BlockSize()); |
| size_t adjust = off % fs_->BlockSize(); |
| |
| while ((len > 0) && (n < kMinfsMaxFileBlock)) { |
| size_t xfer; |
| if (len > (fs_->BlockSize() - adjust)) { |
| xfer = fs_->BlockSize() - adjust; |
| } else { |
| xfer = len; |
| } |
| |
| auto bno_or = BlockGetReadable(n); |
| if (bno_or.is_error()) { |
| return bno_or.take_error(); |
| } |
| if (bno_or.value() != 0) { |
| char bdata[fs_->BlockSize()]; |
| if (auto status = fs_->ReadDat(bno_or.value(), bdata); status.is_error()) { |
| FX_LOGS(ERROR) << "Failed to read data block " << bno_or.value(); |
| return zx::error(ZX_ERR_IO); |
| } |
| memcpy(data, bdata + adjust, xfer); |
| } else { |
| // If the block is not allocated, just read zeros |
| memset(data, 0, xfer); |
| } |
| |
| adjust = 0; |
| len -= xfer; |
| data = data + xfer; |
| n++; |
| } |
| *actual = data - start; |
| #endif |
| return zx::ok(); |
| } |
| |
| // Internal write. Usable on directories. |
| zx::result<> VnodeMinfs::WriteInternal(Transaction* transaction, const uint8_t* data, size_t len, |
| size_t off, size_t* actual) { |
| // We should be called after validating offset and length. Assert if they are invalid. |
| auto new_size_or = safemath::CheckAdd(len, off); |
| ZX_ASSERT(new_size_or.IsValid() && new_size_or.ValueOrDie() <= kMinfsMaxFileSize); |
| |
| if (len == 0) { |
| *actual = 0; |
| return zx::ok(); |
| } |
| #ifdef __Fuchsia__ |
| // TODO(planders): Once we are splitting up write transactions, assert this on host as well. |
| ZX_DEBUG_ASSERT(len <= TransactionLimits::kMaxWriteBytes); |
| if (auto status = InitVmo(); status.is_error()) { |
| return status.take_error(); |
| } |
| |
| #else |
| size_t max_size = off + len; |
| #endif |
| |
| const uint8_t* const start = data; |
| uint32_t n = static_cast<uint32_t>(off / fs_->BlockSize()); |
| size_t adjust = off % fs_->BlockSize(); |
| |
| while (len > 0) { |
| ZX_ASSERT(n < kMinfsMaxFileBlock); |
| size_t xfer; |
| if (len > (fs_->BlockSize() - adjust)) { |
| xfer = fs_->BlockSize() - adjust; |
| } else { |
| xfer = len; |
| } |
| |
| #ifdef __Fuchsia__ |
| size_t xfer_off = n * fs_->BlockSize() + adjust; |
| if ((xfer_off + xfer) > vmo_size_) { |
| size_t new_size = fbl::round_up(xfer_off + xfer, fs_->BlockSize()); |
| ZX_DEBUG_ASSERT(new_size >= GetSize()); // Overflow. |
| if (zx_status_t status = vmo_.set_size(new_size); status != ZX_OK) { |
| break; |
| } |
| vmo_size_ = new_size; |
| } |
| |
| // Update this block of the in-memory VMO |
| if (zx_status_t status = vmo_.write(data, xfer_off, xfer); status != ZX_OK) { |
| break; |
| } |
| |
| if (!DirtyCacheEnabled()) { |
| // Update this block on-disk |
| auto bno_or = BlockGetWritable(transaction, n); |
| if (bno_or.is_error()) { |
| break; |
| } |
| |
| IssueWriteback(transaction, n, bno_or.value() + fs_->Info().dat_block, 1); |
| } |
| #else // __Fuchsia__ |
| auto bno_or = BlockGetWritable(transaction, n); |
| if (bno_or.is_error()) { |
| break; |
| } |
| ZX_DEBUG_ASSERT(bno_or.value() != 0); |
| char wdata[fs_->BlockSize()]; |
| if (auto status = fs_->bc_->Readblk(bno_or.value() + fs_->Info().dat_block, wdata); |
| status.is_error()) { |
| break; |
| } |
| memcpy(wdata + adjust, data, xfer); |
| if (len < fs_->BlockSize() && max_size >= GetSize()) { |
| memset(wdata + adjust + xfer, 0, fs_->BlockSize() - (adjust + xfer)); |
| } |
| if (auto status = fs_->bc_->Writeblk(bno_or.value() + fs_->Info().dat_block, wdata); |
| status.is_error()) { |
| break; |
| } |
| #endif // __Fuchsia__ |
| |
| adjust = 0; |
| len -= xfer; |
| data = data + xfer; |
| n++; |
| } |
| |
| len = data - start; |
| if (len == 0) { |
| // If more than zero bytes were requested, but zero bytes were written, |
| // return an error explicitly (rather than zero). |
| if (off >= kMinfsMaxFileSize) { |
| return zx::error(ZX_ERR_FILE_BIG); |
| } |
| |
| FX_LOGS_FIRST_N(WARNING, 10) << "Minfs::WriteInternal can't write any bytes."; |
| return zx::error(ZX_ERR_NO_SPACE); |
| } |
| |
| if ((off + len) > GetSize()) { |
| SetSize(static_cast<uint32_t>(off + len)); |
| } |
| |
| *actual = len; |
| return zx::ok(); |
| } |
| |
| zx_status_t VnodeMinfs::GetAttributes(fs::VnodeAttributes* a) { |
| FX_LOGS(DEBUG) << "minfs_getattr() vn=" << this << "(#" << ino_ << ")"; |
| return Vfs()->GetNodeOperations()->get_attr.Track([&] { |
| // This transaction exists because acquiring the block size and block |
| // count may be unsafe without locking. |
| // |
| // TODO(unknown): Improve locking semantics of pending data allocation to make this less |
| // confusing. |
| Transaction transaction(fs_); |
| *a = fs::VnodeAttributes(); |
| a->mode = DTYPE_TO_VTYPE(MinfsMagicType(inode_.magic)) | V_IRUSR | V_IWUSR | V_IRGRP | V_IROTH; |
| a->inode = ino_; |
| a->content_size = GetSize(); |
| a->storage_size = GetBlockCount() * fs_->BlockSize(); |
| a->link_count = inode_.link_count; |
| a->creation_time = inode_.create_time; |
| a->modification_time = inode_.modify_time; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t VnodeMinfs::SetAttributes(fs::VnodeAttributesUpdate attr) { |
| int dirty = 0; |
| FX_LOGS(DEBUG) << "minfs_setattr() vn=" << this << "(#" << ino_ << ")"; |
| return Vfs()->GetNodeOperations()->set_attr.Track([&] { |
| if (attr.has_creation_time()) { |
| inode_.create_time = attr.take_creation_time(); |
| dirty = 1; |
| } |
| if (attr.has_modification_time()) { |
| inode_.modify_time = attr.take_modification_time(); |
| dirty = 1; |
| } |
| if (attr.any()) { |
| // any unhandled field update is unsupported |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Commit transaction if dirty cache is disabled. Otherwise this will |
| // happen later. |
| if (dirty && !DirtyCacheEnabled()) { |
| // write to disk, but don't overwrite the time |
| auto transaction_or = fs_->BeginTransaction(0, 0); |
| if (transaction_or.is_error()) { |
| return transaction_or.status_value(); |
| } |
| InodeSync(transaction_or.value().get(), kMxFsSyncDefault); |
| transaction_or->PinVnode(fbl::RefPtr(this)); |
| fs_->CommitTransaction(std::move(transaction_or.value())); |
| } |
| return ZX_OK; |
| }); |
| } |
| |
| VnodeMinfs::VnodeMinfs(Minfs* fs) : fs_(fs) {} |
| |
| #ifdef __Fuchsia__ |
| void VnodeMinfs::Notify(std::string_view name, fuchsia_io::wire::WatchEvent event) { |
| watcher_.Notify(name, event); |
| } |
| zx_status_t VnodeMinfs::WatchDir(fs::FuchsiaVfs* vfs, fuchsia_io::wire::WatchMask mask, |
| uint32_t options, |
| fidl::ServerEnd<fuchsia_io::DirectoryWatcher> watcher) { |
| return watcher_.WatchDir(vfs, this, mask, options, std::move(watcher)); |
| } |
| |
| #endif |
| |
| void VnodeMinfs::Allocate(Minfs* fs, uint32_t type, fbl::RefPtr<VnodeMinfs>* out) { |
| if (type == kMinfsTypeDir) { |
| *out = fbl::AdoptRef(new Directory(fs)); |
| } else { |
| *out = fbl::AdoptRef(new File(fs)); |
| } |
| memset(&(*out)->inode_, 0, sizeof((*out)->inode_)); |
| (*out)->inode_.magic = MinfsMagic(type); |
| (*out)->inode_.create_time = (*out)->inode_.modify_time = GetTimeUTC(); |
| if (type == kMinfsTypeDir) { |
| (*out)->inode_.link_count = 2; |
| // "." and "..". |
| (*out)->inode_.dirent_count = 2; |
| } else { |
| (*out)->inode_.link_count = 1; |
| } |
| } |
| |
| void VnodeMinfs::Recreate(Minfs* fs, ino_t ino, fbl::RefPtr<VnodeMinfs>* out) { |
| Inode inode; |
| fs->InodeLoad(ino, &inode); |
| if (inode.magic == kMinfsMagicDir) { |
| *out = fbl::AdoptRef(new Directory(fs)); |
| } else { |
| *out = fbl::AdoptRef(new File(fs)); |
| } |
| memcpy(&(*out)->inode_, &inode, sizeof(inode)); |
| |
| (*out)->ino_ = ino; |
| (*out)->SetSize(static_cast<uint32_t>((*out)->inode_.size)); |
| } |
| |
| #ifdef __Fuchsia__ |
| |
| zx::result<std::string> VnodeMinfs::GetDevicePath() const { |
| return fs_->bc_->device()->GetTopologicalPath(); |
| } |
| |
| #endif |
| |
| zx::result<> VnodeMinfs::TruncateInternal(Transaction* transaction, size_t len) { |
| // We should be called after validating length. Assert if len is unexpected. |
| ZX_ASSERT(len <= kMinfsMaxFileSize); |
| |
| #ifdef __Fuchsia__ |
| if (auto status = InitVmo(); status.is_error()) { |
| FX_LOGS(ERROR) << "Truncate failed to initialize VMO: " << status.status_value(); |
| return zx::error(ZX_ERR_IO); |
| } |
| #endif |
| |
| uint64_t inode_size = GetSize(); |
| if (len < inode_size) { |
| // Truncate should make the file shorter. |
| blk_t bno = safemath::checked_cast<blk_t>(inode_size / fs_->BlockSize()); |
| |
| // Truncate to the nearest block. |
| blk_t trunc_bno = static_cast<blk_t>(len / fs_->BlockSize()); |
| // [start_bno, EOF) blocks should be deleted entirely. |
| blk_t start_bno = static_cast<blk_t>((len % fs_->BlockSize() == 0) ? trunc_bno : trunc_bno + 1); |
| |
| if (auto shrink_or = BlocksShrink(transaction, start_bno); shrink_or.is_error()) { |
| return shrink_or.take_error(); |
| } |
| |
| #ifdef __Fuchsia__ |
| uint64_t decommit_offset = fbl::round_up(len, fs_->BlockSize()); |
| uint64_t decommit_length = fbl::round_up(inode_size, fs_->BlockSize()) - decommit_offset; |
| if (decommit_length > 0) { |
| zx_status_t status = |
| vmo_.op_range(ZX_VMO_OP_DECOMMIT, decommit_offset, decommit_length, nullptr, 0); |
| if (status != ZX_OK) { |
| // TODO(https://fxbug.dev/42111421): This is a known issue; the additional logging here is |
| // to help diagnose. |
| FX_LOGS(ERROR) << "TruncateInternal: Modifying node length from " << inode_size << " to " |
| << len; |
| FX_LOGS(ERROR) << " Decommit from offset " << decommit_offset << ", length " |
| << decommit_length << ". Status: " << status; |
| ZX_ASSERT(status == ZX_OK); |
| } |
| } |
| #endif |
| // Shrink the size to be block-aligned if we are removing blocks from |
| // the end of the vnode. |
| if (start_bno * fs_->BlockSize() < inode_size) { |
| SetSize(static_cast<uint32_t>(start_bno * fs_->BlockSize())); |
| } |
| |
| // Write zeroes to the rest of the remaining block, if it exists |
| if (len < GetSize()) { |
| char bdata[fs_->BlockSize()]; |
| blk_t rel_bno = static_cast<blk_t>(len / fs_->BlockSize()); |
| |
| if (auto bno_or = BlockGetReadable(rel_bno); bno_or.is_ok()) { |
| bno = bno_or.value(); |
| } else { |
| FX_LOGS(ERROR) << "Truncate failed to get block " << rel_bno |
| << " of file: " << bno_or.status_value(); |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| size_t adjust = len % fs_->BlockSize(); |
| #ifdef __Fuchsia__ |
| bool allocated = (bno != 0); |
| if (allocated || HasPendingAllocation(rel_bno)) { |
| if (zx_status_t status = vmo_.read(bdata, len - adjust, adjust); status != ZX_OK) { |
| FX_LOGS(ERROR) << "Truncate failed to read last block: " << status; |
| return zx::error(ZX_ERR_IO); |
| } |
| memset(bdata + adjust, 0, fs_->BlockSize() - adjust); |
| |
| if (zx_status_t status = vmo_.write(bdata, len - adjust, fs_->BlockSize()); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "Truncate failed to write last block: " << status; |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| if (auto bno_or = BlockGetWritable(transaction, rel_bno); bno_or.is_ok()) { |
| bno = bno_or.value(); |
| } else { |
| FX_LOGS(ERROR) << "Truncate failed to get block " << rel_bno |
| << " of file: " << bno_or.status_value(); |
| return zx::error(ZX_ERR_IO); |
| } |
| IssueWriteback(transaction, rel_bno, bno + fs_->Info().dat_block, 1); |
| } |
| #else // __Fuchsia__ |
| if (bno != 0) { |
| if (fs_->bc_->Readblk(bno + fs_->Info().dat_block, bdata).is_error()) { |
| return zx::error(ZX_ERR_IO); |
| } |
| memset(bdata + adjust, 0, fs_->BlockSize() - adjust); |
| if (fs_->bc_->Writeblk(bno + fs_->Info().dat_block, bdata).is_error()) { |
| return zx::error(ZX_ERR_IO); |
| } |
| } |
| #endif // __Fuchsia__ |
| } |
| } else if (len > inode_size) { |
| // Truncate should make the file longer, filled with zeroes. |
| if (kMinfsMaxFileSize < len) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| #ifdef __Fuchsia__ |
| uint64_t new_size = fbl::round_up(len, fs_->BlockSize()); |
| if (zx_status_t status = vmo_.set_size(new_size); status != ZX_OK) { |
| return zx::error(status); |
| } |
| vmo_size_ = new_size; |
| #endif |
| } else { |
| return zx::ok(); |
| } |
| |
| // Setting the size does not ensure the on-disk inode is updated. Ensuring |
| // writeback occurs is the responsibility of the caller. |
| SetSize(static_cast<uint32_t>(len)); |
| return zx::ok(); |
| } |
| |
| #ifdef __Fuchsia__ |
| |
| void VnodeMinfs::Sync(SyncCallback closure) { |
| TRACE_DURATION("minfs", "VnodeMinfs::Sync"); |
| auto event = Vfs()->GetNodeOperations()->sync.NewEvent(); |
| // The transaction may go async in journal layer. Hold the reference over this |
| // vnode so that we keep the vnode around until the transaction is complete. |
| auto vn = fbl::RefPtr(this); |
| fs_->Sync([vn, cb = std::move(closure), event = std::move(event)](zx_status_t status) mutable { |
| // This is called on the journal thread. Operations here must be threadsafe. |
| if (status == ZX_OK) { |
| status = vn->fs_->bc_->Sync().status_value(); |
| } |
| cb(status); |
| event.SetStatus(status); |
| }); |
| } |
| |
| #endif |
| |
| } // namespace minfs |