| // Copyright 2021 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 <dirent.h> |
| |
| #include "src/storage/f2fs/bcache.h" |
| #include "src/storage/f2fs/dir.h" |
| #include "src/storage/f2fs/f2fs.h" |
| #include "src/storage/f2fs/file.h" |
| #include "src/storage/f2fs/node.h" |
| #include "src/storage/f2fs/node_page.h" |
| #include "src/storage/f2fs/superblock_info.h" |
| |
| namespace f2fs { |
| |
| uint8_t *Dir::InlineDentryBitmap(Page *page) { |
| Inode &inode = page->GetAddress<Node>()->i; |
| return reinterpret_cast<uint8_t *>( |
| &inode.i_addr[extra_isize_ / sizeof(uint32_t) + kInlineStartOffset]); |
| } |
| |
| uint64_t Dir::InlineDentryBitmapSize() const { |
| return CheckedDivRoundUp<uint64_t>(MaxInlineDentry(), kBitsPerByte); |
| } |
| |
| DirEntry *Dir::InlineDentryArray(Page *page, VnodeF2fs &vnode) { |
| uint8_t *base = InlineDentryBitmap(page); |
| size_t reserved = safemath::checked_cast<uint32_t>( |
| (vnode.MaxInlineData() - |
| safemath::CheckMul(vnode.MaxInlineDentry(), (kSizeOfDirEntry + kDentrySlotLen))) |
| .ValueOrDie()); |
| |
| return reinterpret_cast<DirEntry *>(base + reserved); |
| } |
| |
| char (*Dir::InlineDentryFilenameArray(Page *page, VnodeF2fs &vnode))[kDentrySlotLen] { |
| uint8_t *base = InlineDentryBitmap(page); |
| size_t reserved = safemath::checked_cast<uint32_t>( |
| (vnode.MaxInlineData() - safemath::CheckMul(vnode.MaxInlineDentry(), kDentrySlotLen)) |
| .ValueOrDie()); |
| return reinterpret_cast<char(*)[kDentrySlotLen]>(base + reserved); |
| } |
| |
| zx::result<DentryInfo> Dir::FindInInlineDir(std::string_view name, fbl::RefPtr<Page> *res_page) { |
| LockedPage ipage; |
| if (zx_status_t ret = fs()->GetNodeManager().GetNodePage(Ino(), &ipage); ret != ZX_OK) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| std::vector<nid_t> nids; |
| size_t n = 0; |
| f2fs_hash_t namehash = DentryHash(name); |
| auto bits = GetBitmap(ipage.CopyRefPtr()); |
| ZX_DEBUG_ASSERT(bits.is_ok()); |
| DentryInfo ret = {kNullIno, kCachedInlineDirEntryPageIndex, 0}; |
| res_page->reset(); |
| for (size_t bit_pos = 0; (bit_pos = bits->FindNextBit(bit_pos)) < MaxInlineDentry();) { |
| const DirEntry &de = InlineDentryArray(ipage.get(), *this)[bit_pos]; |
| std::string_view entry_name(InlineDentryFilenameArray(ipage.get(), *this)[bit_pos], |
| LeToCpu(de.name_len)); |
| nid_t ino = LeToCpu(de.ino); |
| if (!*res_page) { |
| if (EarlyMatchName(name, namehash, de) && |
| !memcmp(entry_name.data(), name.data(), name.length())) { |
| ret = {ino, kCachedInlineDirEntryPageIndex, bit_pos}; |
| *res_page = ipage.CopyRefPtr(); |
| } |
| } |
| if (*res_page) { |
| nids.push_back(ino); |
| GetDirEntryCache().UpdateDirEntry(entry_name, {ino, kCachedInlineDirEntryPageIndex, bit_pos}); |
| if (kDefaultNodeReadSize <= ++n) { |
| break; |
| } |
| } |
| // For the most part, it should be a bug when name_len is zero. |
| // We stop here for figuring out where the bugs are occurred. |
| ZX_DEBUG_ASSERT(de.name_len > 0); |
| bit_pos += GetDentrySlots(LeToCpu(de.name_len)); |
| } |
| if (!*res_page) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| ipage.reset(); |
| if (nids.size() > 1) { |
| ZX_ASSERT(fs()->GetNodeManager().GetNodePages(nids).is_ok()); |
| } |
| return zx::ok(ret); |
| } |
| |
| zx_status_t Dir::MakeEmptyInlineDir(ino_t parent_ino) { |
| LockedPage ipage; |
| |
| if (zx_status_t err = fs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) |
| return err; |
| |
| DirEntry *de = &InlineDentryArray(&(*ipage), *this)[0]; |
| de->name_len = CpuToLe(static_cast<uint16_t>(1)); |
| de->hash_code = 0; |
| de->ino = CpuToLe(Ino()); |
| std::memcpy(InlineDentryFilenameArray(&(*ipage), *this)[0], ".", 1); |
| SetDirEntryType(*de, *this); |
| |
| de = &InlineDentryArray(&(*ipage), *this)[1]; |
| de->hash_code = 0; |
| de->name_len = CpuToLe(static_cast<uint16_t>(2)); |
| de->ino = CpuToLe(parent_ino); |
| std::memcpy(InlineDentryFilenameArray(&(*ipage), *this)[1], "..", 2); |
| SetDirEntryType(*de, *this); |
| |
| auto bits = GetBitmap(ipage.CopyRefPtr()); |
| ZX_DEBUG_ASSERT(bits.is_ok()); |
| bits->Set(0); |
| bits->Set(1); |
| |
| ipage.SetDirty(); |
| |
| if (GetSize() < MaxInlineData()) { |
| SetSize(MaxInlineData()); |
| SetFlag(InodeInfoFlag::kUpdateDir); |
| } |
| |
| return ZX_OK; |
| } |
| |
| size_t Dir::RoomInInlineDir(const PageBitmap &bits, size_t slots) { |
| size_t bit_start = 0; |
| while (true) { |
| size_t zero_start = bits.FindNextZeroBit(bit_start); |
| if (zero_start >= MaxInlineDentry()) |
| return MaxInlineDentry(); |
| |
| size_t zero_end = bits.FindNextBit(zero_start); |
| if (zero_end - zero_start >= slots) |
| return zero_start; |
| |
| bit_start = zero_end + 1; |
| if (bit_start >= MaxInlineDentry()) { |
| return MaxInlineDentry(); |
| } |
| } |
| } |
| |
| zx_status_t Dir::ConvertInlineDir() { |
| LockedPage page; |
| if (zx_status_t ret = GrabLockedPage(0, &page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| auto path_or = GetNodePath(0); |
| if (path_or.is_error()) { |
| return path_or.error_value(); |
| } |
| auto dnode_page_or = fs()->GetNodeManager().GetLockedDnodePage(*path_or, IsDir()); |
| if (dnode_page_or.is_error()) { |
| return dnode_page_or.error_value(); |
| } |
| IncBlocks(path_or->num_new_nodes); |
| LockedPage dnode_page = std::move(*dnode_page_or); |
| size_t ofs_in_dnode = GetOfsInDnode(*path_or); |
| NodePage *ipage = &dnode_page.GetPage<NodePage>(); |
| block_t data_blkaddr = ipage->GetBlockAddr(ofs_in_dnode); |
| ZX_DEBUG_ASSERT(data_blkaddr == kNullAddr); |
| |
| if (zx_status_t err = ReserveNewBlock(dnode_page, ofs_in_dnode); err != ZX_OK) { |
| return err; |
| } |
| |
| page.WaitOnWriteback(); |
| page.Zero(); |
| |
| DentryBlock *dentry_blk = page->GetAddress<DentryBlock>(); |
| |
| // copy data from inline dentry block to new dentry block |
| std::memcpy(dentry_blk->dentry_bitmap, InlineDentryBitmap(ipage), InlineDentryBitmapSize()); |
| std::memcpy(dentry_blk->dentry, InlineDentryArray(ipage, *this), |
| sizeof(DirEntry) * MaxInlineDentry()); |
| std::memcpy( |
| dentry_blk->filename, InlineDentryFilenameArray(ipage, *this), |
| safemath::CheckMul(safemath::checked_cast<size_t>(MaxInlineDentry()), kNameLen).ValueOrDie()); |
| |
| page.SetDirty(); |
| // clear inline dir and flag after data writeback |
| dnode_page.WaitOnWriteback(); |
| dnode_page.Zero(InlineDataOffset(), InlineDataOffset() + MaxInlineData()); |
| ClearFlag(InodeInfoFlag::kInlineDentry); |
| |
| if (!TestFlag(InodeInfoFlag::kInlineXattr)) { |
| inline_xattr_size_ = 0; |
| } |
| |
| if (GetSize() < kPageSize) { |
| SetSize(kPageSize); |
| SetFlag(InodeInfoFlag::kUpdateDir); |
| } |
| |
| SetDirty(); |
| #if 0 // porting needed |
| // stat_dec_inline_inode(dir); |
| #endif |
| return ZX_OK; |
| } |
| |
| zx::result<bool> Dir::AddInlineEntry(std::string_view name, VnodeF2fs *vnode) { |
| { |
| LockedPage ipage; |
| if (zx_status_t err = fs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) { |
| return zx::error(err); |
| } |
| |
| f2fs_hash_t name_hash = DentryHash(name); |
| uint16_t slots = GetDentrySlots(safemath::checked_cast<uint16_t>(name.length())); |
| auto bits = GetBitmap(ipage.CopyRefPtr()); |
| ZX_DEBUG_ASSERT(bits.is_ok()); |
| size_t bit_pos = RoomInInlineDir(*bits, slots); |
| if (bit_pos + slots <= MaxInlineDentry()) { |
| ipage.WaitOnWriteback(); |
| |
| if (zx_status_t err = vnode->InitInodeMetadata(); err != ZX_OK) { |
| if (TestFlag(InodeInfoFlag::kUpdateDir)) { |
| ClearFlag(InodeInfoFlag::kUpdateDir); |
| SetDirty(); |
| } |
| return zx::error(err); |
| } |
| |
| DirEntry &de = InlineDentryArray(ipage.get(), *this)[bit_pos]; |
| de.hash_code = name_hash; |
| de.name_len = static_cast<uint16_t>(CpuToLe(name.length())); |
| std::memcpy(InlineDentryFilenameArray(ipage.get(), *this)[bit_pos], name.data(), |
| name.length()); |
| de.ino = CpuToLe(vnode->Ino()); |
| SetDirEntryType(de, *vnode); |
| for (int i = 0; i < slots; ++i) { |
| bits->Set(bit_pos + i); |
| } |
| DentryInfo info = {vnode->Ino(), kCachedInlineDirEntryPageIndex, bit_pos}; |
| GetDirEntryCache().UpdateDirEntry(name, info); |
| ipage.SetDirty(); |
| UpdateParentMetadata(vnode, 0); |
| vnode->SetDirty(); |
| SetDirty(); |
| |
| ClearFlag(InodeInfoFlag::kUpdateDir); |
| return zx::ok(false); |
| } |
| } |
| |
| if (auto ret = ConvertInlineDir(); ret != ZX_OK) { |
| return zx::error(ret); |
| } |
| return zx::ok(true); |
| } |
| |
| void Dir::DeleteInlineEntry(const DentryInfo &info, fbl::RefPtr<Page> &page, VnodeF2fs *vnode) { |
| LockedPage lock_page(page); |
| lock_page.WaitOnWriteback(); |
| |
| ZX_DEBUG_ASSERT(info.page_index == kCachedInlineDirEntryPageIndex); |
| DirEntry &dentry = InlineDentryArray(lock_page.get(), *this)[info.bit_pos]; |
| int slots = GetDentrySlots(LeToCpu(dentry.name_len)); |
| auto bits = GetBitmap(lock_page.CopyRefPtr()); |
| ZX_DEBUG_ASSERT(bits.is_ok()); |
| for (int i = 0; i < slots; ++i) { |
| bits->Clear(info.bit_pos + i); |
| } |
| |
| lock_page.SetDirty(); |
| |
| std::string_view remove_name( |
| reinterpret_cast<char *>(InlineDentryFilenameArray(lock_page.get(), *this)[info.bit_pos]), |
| LeToCpu(dentry.name_len)); |
| |
| GetDirEntryCache().RemoveDirEntry(remove_name); |
| |
| time_->Update<Timestamps::ModificationTime>(); |
| |
| if (vnode && vnode->IsDir()) { |
| DropNlink(); |
| } |
| |
| if (vnode) { |
| vnode->SetDirty(); |
| vnode->SetTime<Timestamps::ChangeTime>(); |
| vnode->DropNlink(); |
| if (vnode->IsDir()) { |
| vnode->DropNlink(); |
| vnode->SetSize(0); |
| } |
| if (vnode->GetNlink() == 0) { |
| vnode->SetOrphan(); |
| } |
| } |
| SetDirty(); |
| } |
| |
| bool Dir::IsEmptyInlineDir() { |
| LockedPage ipage; |
| |
| if (zx_status_t err = fs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) |
| return false; |
| |
| size_t bit_pos = 2; |
| auto bits = GetBitmap(ipage.CopyRefPtr()); |
| ZX_DEBUG_ASSERT(bits.is_ok()); |
| bit_pos = bits->FindNextBit(bit_pos); |
| return bit_pos >= MaxInlineDentry(); |
| } |
| |
| zx_status_t Dir::ReadInlineDir(fs::VdirCookie *cookie, void *dirents, size_t len, |
| size_t *out_actual) { |
| fs::DirentFiller df(dirents, len); |
| uint64_t *pos_cookie = reinterpret_cast<uint64_t *>(cookie); |
| |
| if (*pos_cookie == MaxInlineDentry()) { |
| *out_actual = 0; |
| return ZX_OK; |
| } |
| |
| LockedPage ipage; |
| |
| if (zx_status_t err = fs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) |
| return err; |
| |
| const unsigned char *types = kFiletypeTable; |
| size_t bit_pos = *pos_cookie % MaxInlineDentry(); |
| auto bits = GetBitmap(ipage.CopyRefPtr()); |
| ZX_DEBUG_ASSERT(bits.is_ok()); |
| |
| while (bit_pos < MaxInlineDentry()) { |
| if (bit_pos = bits->FindNextBit(bit_pos); bit_pos >= MaxInlineDentry()) { |
| break; |
| } |
| |
| DirEntry *de = &InlineDentryArray(ipage.get(), *this)[bit_pos]; |
| unsigned char d_type = DT_UNKNOWN; |
| if (de->file_type < static_cast<uint8_t>(FileType::kFtMax)) |
| d_type = types[de->file_type]; |
| |
| std::string_view name(InlineDentryFilenameArray(ipage.get(), *this)[bit_pos], |
| LeToCpu(de->name_len)); |
| |
| if (de->ino && name != "..") { |
| if (zx_status_t ret = df.Next(name, d_type, LeToCpu(de->ino)); ret != ZX_OK) { |
| *pos_cookie = bit_pos; |
| |
| *out_actual = df.BytesFilled(); |
| return ZX_OK; |
| } |
| } |
| |
| bit_pos += GetDentrySlots(LeToCpu(de->name_len)); |
| } |
| |
| *pos_cookie = MaxInlineDentry(); |
| *out_actual = df.BytesFilled(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t File::ConvertInlineData() { |
| if (!TestFlag(InodeInfoFlag::kInlineData)) { |
| return ZX_OK; |
| } |
| LockedPage page; |
| if (TestFlag(InodeInfoFlag::kDataExist)) { |
| if (zx_status_t ret = GrabLockedPage(0, &page); ret != ZX_OK) { |
| return ret; |
| } |
| } |
| |
| auto path_or = GetNodePath(0); |
| if (path_or.is_error()) { |
| return path_or.error_value(); |
| } |
| auto dnode_page_or = fs()->GetNodeManager().GetLockedDnodePage(*path_or, IsDir()); |
| if (dnode_page_or.is_error()) { |
| return dnode_page_or.error_value(); |
| } |
| IncBlocks(path_or->num_new_nodes); |
| LockedPage dnode_page = std::move(*dnode_page_or); |
| size_t ofs_in_dnode = GetOfsInDnode(*path_or); |
| NodePage *ipage = &dnode_page.GetPage<NodePage>(); |
| block_t data_blkaddr = ipage->GetBlockAddr(ofs_in_dnode); |
| ZX_DEBUG_ASSERT(data_blkaddr == kNullAddr); |
| |
| if (zx_status_t err = ReserveNewBlock(dnode_page, ofs_in_dnode); err != ZX_OK) { |
| return err; |
| } |
| |
| if (TestFlag(InodeInfoFlag::kDataExist)) { |
| page.WaitOnWriteback(); |
| page->Write(ipage->GetAddress<uint8_t>() + InlineDataOffset(), 0, GetSize()); |
| page.SetDirty(); |
| |
| dnode_page.WaitOnWriteback(); |
| dnode_page.Zero(InlineDataOffset(), InlineDataOffset() + MaxInlineData()); |
| } |
| ClearFlag(InodeInfoFlag::kInlineData); |
| ClearFlag(InodeInfoFlag::kDataExist); |
| |
| SetDirty(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t File::WriteInline(const void *data, size_t len, size_t offset, size_t *out_actual) { |
| LockedPage inline_page; |
| if (zx_status_t ret = fs()->GetNodeManager().GetNodePage(Ino(), &inline_page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| inline_page.WaitOnWriteback(); |
| inline_page->Write(data, InlineDataOffset() + offset, len); |
| |
| SetSize(std::max(GetSize(), offset + len)); |
| SetFlag(InodeInfoFlag::kDataExist); |
| inline_page.SetDirty(); |
| |
| SetTime<Timestamps::ModificationTime>(); |
| SetDirty(); |
| |
| *out_actual = len; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t File::TruncateInline(size_t len, bool is_recover) { |
| LockedPage inline_page; |
| if (zx_status_t ret = fs()->GetNodeManager().GetNodePage(Ino(), &inline_page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| inline_page.WaitOnWriteback(); |
| |
| size_t size = GetSize(); |
| size_t size_diff = (len > size) ? (len - size) : (size - len); |
| size_t offset = InlineDataOffset() + ((len > size) ? size : len); |
| inline_page.Zero(offset, offset + size_diff); |
| |
| // When removing inline data during recovery, file size should not be modified. |
| if (!is_recover) { |
| SetSize(len); |
| } |
| if (len == 0) { |
| ClearFlag(InodeInfoFlag::kDataExist); |
| } |
| inline_page.SetDirty(); |
| |
| SetTime<Timestamps::ModificationTime>(); |
| SetDirty(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t File::RecoverInlineData(NodePage &page) { |
| // The inline_data recovery policy is as follows. |
| // [prev.] [next] of inline_data flag |
| // o o -> recover inline_data |
| // o x -> remove inline_data, and then recover data blocks |
| // x o -> remove data blocks, and then recover inline_data (not happen) |
| // x x -> recover data blocks |
| // ([prev.] is checkpointed data. And [next] is data written and fsynced after checkpoint.) |
| |
| if (page.IsInode()) { |
| Inode &inode = page.GetAddress<Node>()->i; |
| |
| // [next] have inline data. |
| if (inode.i_inline & kInlineData) { |
| // Process inline. |
| LockedPage ipage; |
| if (zx_status_t err = fs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) { |
| return err; |
| } |
| BlockBuffer block; |
| ipage.WaitOnWriteback(); |
| page.Read(block.get(), InlineDataOffset(), MaxInlineData()); |
| ipage->Write(block.get(), InlineDataOffset(), MaxInlineData()); |
| |
| SetFlag(InodeInfoFlag::kInlineData); |
| SetFlag(InodeInfoFlag::kDataExist); |
| |
| ipage.SetDirty(); |
| return ZX_OK; |
| } |
| } |
| |
| // [prev.] has inline data but [next] has no inline data. |
| if (TestFlag(InodeInfoFlag::kInlineData)) { |
| TruncateInline(0, true); |
| ClearFlag(InodeInfoFlag::kInlineData); |
| ClearFlag(InodeInfoFlag::kDataExist); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| } // namespace f2fs |