| // 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/f2fs.h" |
| |
| namespace f2fs { |
| |
| uint32_t Dir::MaxInlineDentry() const { |
| return MaxInlineData() * kBitsPerByte / ((kSizeOfDirEntry + kDentrySlotLen) * kBitsPerByte + 1); |
| } |
| |
| uint8_t *Dir::InlineDentryBitmap(Page *page) { |
| Node *rn = page->GetAddress<Node>(); |
| Inode &ri = rn->i; |
| return reinterpret_cast<uint8_t *>( |
| &ri.i_addr[GetExtraISize() / sizeof(uint32_t) + kInlineStartOffset]); |
| } |
| |
| uint64_t Dir::InlineDentryBitmapSize() const { |
| return (MaxInlineDentry() + kBitsPerByte - 1) / kBitsPerByte; |
| } |
| |
| DirEntry *Dir::InlineDentryArray(Page *page) { |
| uint8_t *base = InlineDentryBitmap(page); |
| uint32_t reserved = MaxInlineData() - MaxInlineDentry() * (kSizeOfDirEntry + kDentrySlotLen); |
| return reinterpret_cast<DirEntry *>(base + reserved); |
| } |
| |
| uint8_t (*Dir::InlineDentryFilenameArray(Page *page))[kDentrySlotLen] { |
| uint8_t *base = InlineDentryBitmap(page); |
| uint32_t reserved = MaxInlineData() - MaxInlineDentry() * kDentrySlotLen; |
| return reinterpret_cast<uint8_t(*)[kDentrySlotLen]>(base + reserved); |
| } |
| |
| DirEntry *Dir::FindInInlineDir(std::string_view name, fbl::RefPtr<Page> *res_page) { |
| LockedPage ipage; |
| if (zx_status_t ret = Vfs()->GetNodeManager().GetNodePage(Ino(), &ipage); ret != ZX_OK) |
| return nullptr; |
| f2fs_hash_t namehash = DentryHash(name); |
| |
| for (uint32_t bit_pos = 0; bit_pos < MaxInlineDentry();) { |
| bit_pos = FindNextBit(InlineDentryBitmap(ipage.get()), MaxInlineDentry(), bit_pos); |
| if (bit_pos >= MaxInlineDentry()) { |
| break; |
| } |
| |
| DirEntry *de = &InlineDentryArray(ipage.get())[bit_pos]; |
| if (EarlyMatchName(name, namehash, *de)) { |
| if (!memcmp(InlineDentryFilenameArray(ipage.get())[bit_pos], name.data(), name.length())) { |
| *res_page = ipage.release(); |
| |
| #ifdef __Fuchsia__ |
| if (de != nullptr) { |
| Vfs()->GetDirEntryCache().UpdateDirEntry(Ino(), name, *de, |
| kCachedInlineDirEntryPageIndex); |
| } |
| #endif // __Fuchsia__ |
| return de; |
| } |
| } |
| |
| // 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. |
| #if 0 // porting needed |
| // f2fs_bug_on(F2FS_P_SB(node_page), !de->name_len); |
| #else |
| ZX_ASSERT(de->name_len > 0); |
| #endif |
| |
| bit_pos += GetDentrySlots(LeToCpu(de->name_len)); |
| } |
| |
| return nullptr; |
| } |
| |
| DirEntry *Dir::ParentInlineDir(fbl::RefPtr<Page> *out) { |
| LockedPage ipage; |
| if (zx_status_t ret = Vfs()->GetNodeManager().GetNodePage(Ino(), &ipage); ret != ZX_OK) { |
| return nullptr; |
| } |
| |
| DirEntry *de = &InlineDentryArray(ipage.get())[1]; |
| *out = ipage.release(); |
| return de; |
| } |
| |
| zx_status_t Dir::MakeEmptyInlineDir(VnodeF2fs *vnode) { |
| LockedPage ipage; |
| |
| if (zx_status_t err = Vfs()->GetNodeManager().GetNodePage(vnode->Ino(), &ipage); err != ZX_OK) |
| return err; |
| |
| DirEntry *de = &InlineDentryArray(&(*ipage))[0]; |
| de->name_len = CpuToLe(static_cast<uint16_t>(1)); |
| de->hash_code = 0; |
| de->ino = CpuToLe(vnode->Ino()); |
| memcpy(InlineDentryFilenameArray(&(*ipage))[0], ".", 1); |
| SetDeType(de, vnode); |
| |
| de = &InlineDentryArray(&(*ipage))[1]; |
| de->hash_code = 0; |
| de->name_len = CpuToLe(static_cast<uint16_t>(2)); |
| de->ino = CpuToLe(Ino()); |
| memcpy(InlineDentryFilenameArray(&(*ipage))[1], "..", 2); |
| SetDeType(de, vnode); |
| |
| TestAndSetBit(0, InlineDentryBitmap(&(*ipage))); |
| TestAndSetBit(1, InlineDentryBitmap(&(*ipage))); |
| |
| ipage->SetDirty(); |
| |
| if (vnode->GetSize() < vnode->MaxInlineData()) { |
| vnode->SetSize(vnode->MaxInlineData()); |
| vnode->SetFlag(InodeInfoFlag::kUpdateDir); |
| } |
| |
| return ZX_OK; |
| } |
| |
| unsigned int Dir::RoomInInlineDir(Page *ipage, int slots) { |
| int bit_start = 0; |
| |
| while (true) { |
| int zero_start = FindNextZeroBit(InlineDentryBitmap(ipage), MaxInlineDentry(), bit_start); |
| if (zero_start >= static_cast<int>(MaxInlineDentry())) |
| return MaxInlineDentry(); |
| |
| int zero_end = FindNextBit(InlineDentryBitmap(ipage), MaxInlineDentry(), zero_start); |
| if (zero_end - zero_start >= slots) |
| return zero_start; |
| |
| bit_start = zero_end + 1; |
| |
| if (zero_end + 1 >= static_cast<int>(MaxInlineDentry())) { |
| return MaxInlineDentry(); |
| } |
| } |
| } |
| |
| zx_status_t Dir::ConvertInlineDir() { |
| LockedPage page; |
| if (zx_status_t ret = GrabCachePage(0, &page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| LockedPage dnode_page; |
| if (zx_status_t err = Vfs()->GetNodeManager().GetLockedDnodePage(*this, 0, &dnode_page); |
| err != ZX_OK) { |
| return err; |
| } |
| |
| uint32_t ofs_in_dnode; |
| if (auto result = Vfs()->GetNodeManager().GetOfsInDnode(*this, 0); result.is_error()) { |
| return result.error_value(); |
| } else { |
| ofs_in_dnode = result.value(); |
| } |
| |
| NodePage *ipage = &dnode_page.GetPage<NodePage>(); |
| block_t data_blkaddr = DatablockAddr(ipage, ofs_in_dnode); |
| if (data_blkaddr == kNullAddr) { |
| if (zx_status_t err = ReserveNewBlock(*ipage, ofs_in_dnode); err != ZX_OK) { |
| return err; |
| } |
| data_blkaddr = kNewAddr; |
| } |
| |
| page->WaitOnWriteback(); |
| page->ZeroUserSegment(0, kPageSize); |
| |
| DentryBlock *dentry_blk = page->GetAddress<DentryBlock>(); |
| |
| // copy data from inline dentry block to new dentry block |
| memcpy(dentry_blk->dentry_bitmap, InlineDentryBitmap(ipage), InlineDentryBitmapSize()); |
| memcpy(dentry_blk->dentry, InlineDentryArray(ipage), sizeof(DirEntry) * MaxInlineDentry()); |
| memcpy(dentry_blk->filename, InlineDentryFilenameArray(ipage), MaxInlineDentry() * kNameLen); |
| |
| #if 0 // porting needed |
| // kunmap(page); |
| #endif |
| page->SetDirty(); |
| // TODO: Use writeback() while keeping the lock |
| if (page->ClearDirtyForIo()) { |
| page->SetWriteback(); |
| Vfs()->GetSegmentManager().WriteDataPage(this, page, ipage->NidOfNode(), ofs_in_dnode, |
| data_blkaddr, &data_blkaddr); |
| SetDataBlkaddr(*ipage, ofs_in_dnode, data_blkaddr); |
| UpdateExtentCache(data_blkaddr, 0); |
| UpdateVersion(); |
| } |
| // clear inline dir and flag after data writeback |
| ipage->WaitOnWriteback(); |
| ipage->ZeroUserSegment(InlineDataOffset(), InlineDataOffset() + MaxInlineData()); |
| ClearFlag(InodeInfoFlag::kInlineDentry); |
| |
| if (GetSize() < kPageSize) { |
| SetSize(kPageSize); |
| SetFlag(InodeInfoFlag::kUpdateDir); |
| } |
| |
| UpdateInode(ipage); |
| #if 0 // porting needed |
| // stat_dec_inline_inode(dir); |
| #endif |
| return ZX_OK; |
| } |
| |
| zx_status_t Dir::AddInlineEntry(std::string_view name, VnodeF2fs *vnode, bool *is_converted) { |
| *is_converted = false; |
| |
| f2fs_hash_t name_hash = DentryHash(name); |
| |
| LockedPage ipage; |
| if (zx_status_t err = Vfs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) { |
| return err; |
| } |
| |
| int slots = GetDentrySlots(static_cast<uint16_t>(name.length())); |
| unsigned int bit_pos = RoomInInlineDir(ipage.get(), slots); |
| if (bit_pos >= MaxInlineDentry()) { |
| ipage.reset(); |
| ZX_ASSERT(ConvertInlineDir() == ZX_OK); |
| |
| *is_converted = true; |
| return ZX_OK; |
| } |
| |
| ipage->WaitOnWriteback(); |
| |
| #if 0 // porting needed |
| // down_write(&F2FS_I(inode)->i_sem); |
| #endif |
| |
| if (zx_status_t err = InitInodeMetadata(vnode); err != ZX_OK) { |
| #if 0 // porting needed |
| // up_write(&F2FS_I(inode)->i_sem); |
| #endif |
| |
| if (TestFlag(InodeInfoFlag::kUpdateDir)) { |
| UpdateInode(ipage.get()); |
| ClearFlag(InodeInfoFlag::kUpdateDir); |
| } |
| return err; |
| } |
| |
| DirEntry *de = &InlineDentryArray(ipage.get())[bit_pos]; |
| de->hash_code = name_hash; |
| de->name_len = static_cast<uint16_t>(CpuToLe(name.length())); |
| memcpy(InlineDentryFilenameArray(ipage.get())[bit_pos], name.data(), name.length()); |
| de->ino = CpuToLe(vnode->Ino()); |
| SetDeType(de, vnode); |
| for (int i = 0; i < slots; ++i) { |
| TestAndSetBit(bit_pos + i, InlineDentryBitmap(ipage.get())); |
| } |
| |
| #ifdef __Fuchsia__ |
| if (de != nullptr) { |
| Vfs()->GetDirEntryCache().UpdateDirEntry(Ino(), name, *de, kCachedInlineDirEntryPageIndex); |
| } |
| #endif // __Fuchsia__ |
| |
| ipage->SetDirty(); |
| UpdateParentMetadata(vnode, 0); |
| vnode->WriteInode(); |
| UpdateInode(ipage.get()); |
| |
| #if 0 // porting needed |
| // up_write(&F2FS_I(inode)->i_sem); |
| #endif |
| |
| if (TestFlag(InodeInfoFlag::kUpdateDir)) { |
| ClearFlag(InodeInfoFlag::kUpdateDir); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void Dir::DeleteInlineEntry(DirEntry *dentry, fbl::RefPtr<Page> &page, VnodeF2fs *vnode) { |
| LockedPage lock_page(page); |
| page->WaitOnWriteback(); |
| |
| unsigned int bit_pos = static_cast<uint32_t>(dentry - InlineDentryArray(page.get())); |
| int slots = GetDentrySlots(LeToCpu(dentry->name_len)); |
| for (int i = 0; i < slots; ++i) { |
| TestAndClearBit(bit_pos + i, InlineDentryBitmap(page.get())); |
| } |
| |
| page->SetDirty(); |
| |
| #ifdef __Fuchsia__ |
| std::string_view remove_name( |
| reinterpret_cast<char *>(InlineDentryFilenameArray(page.get())[bit_pos]), |
| LeToCpu(dentry->name_len)); |
| |
| Vfs()->GetDirEntryCache().RemoveDirEntry(Ino(), remove_name); |
| #endif // __Fuchsia__ |
| |
| timespec cur_time; |
| clock_gettime(CLOCK_REALTIME, &cur_time); |
| SetCTime(cur_time); |
| SetMTime(cur_time); |
| |
| if (vnode && vnode->IsDir()) { |
| DropNlink(); |
| } |
| |
| if (vnode) { |
| clock_gettime(CLOCK_REALTIME, &cur_time); |
| SetCTime(cur_time); |
| SetMTime(cur_time); |
| vnode->SetCTime(cur_time); |
| vnode->DropNlink(); |
| if (vnode->IsDir()) { |
| vnode->DropNlink(); |
| vnode->InitSize(); |
| } |
| vnode->WriteInode(false); |
| if (vnode->GetNlink() == 0) { |
| Vfs()->AddOrphanInode(vnode); |
| } |
| } |
| UpdateInode(page.get()); |
| } |
| |
| bool Dir::IsEmptyInlineDir() { |
| LockedPage ipage; |
| |
| if (zx_status_t err = Vfs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) |
| return false; |
| |
| unsigned int bit_pos = 2; |
| bit_pos = FindNextBit(InlineDentryBitmap(ipage.get()), MaxInlineDentry(), bit_pos); |
| |
| if (bit_pos < MaxInlineDentry()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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 = Vfs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) |
| return err; |
| |
| const unsigned char *types = kFiletypeTable; |
| uint32_t bit_pos = *pos_cookie % MaxInlineDentry(); |
| |
| while (bit_pos < MaxInlineDentry()) { |
| bit_pos = FindNextBit(InlineDentryBitmap(ipage.get()), MaxInlineDentry(), bit_pos); |
| if (bit_pos >= MaxInlineDentry()) { |
| break; |
| } |
| |
| DirEntry *de = &InlineDentryArray(ipage.get())[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(reinterpret_cast<char *>(InlineDentryFilenameArray(ipage.get())[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; |
| } |
| |
| uint8_t *File::InlineDataPtr(Page *page) { |
| Node *rn = page->GetAddress<Node>(); |
| Inode &ri = rn->i; |
| return reinterpret_cast<uint8_t *>( |
| &ri.i_addr[GetExtraISize() / sizeof(uint32_t) + kInlineStartOffset]); |
| } |
| |
| #ifdef __Fuchsia__ |
| zx::status<> File::PopulateVmoWithInlineData(zx::vmo &vmo) { |
| LockedPage inline_page; |
| if (zx_status_t ret = Vfs()->GetNodeManager().GetNodePage(Ino(), &inline_page); ret != ZX_OK) { |
| return zx::error(ret); |
| } |
| // Fill |vmo| only when it has valid inline data. |
| if (TestFlag(InodeInfoFlag::kDataExist)) { |
| uint8_t *inline_data = InlineDataPtr(inline_page.get()); |
| size_t size = GetSize(); |
| if (size) { |
| vmo.write(inline_data, 0, size); |
| } |
| } |
| inline_page->SetMmapped(); |
| return zx::ok(); |
| } |
| #endif // __Fuchsia__ |
| |
| zx_status_t File::ReadInline(void *data, size_t len, size_t off, size_t *out_actual) { |
| LockedPage inline_page; |
| if (zx_status_t ret = Vfs()->GetNodeManager().GetNodePage(Ino(), &inline_page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| uint8_t *inline_data = InlineDataPtr(inline_page.get()); |
| size_t cur_len = std::min(len, GetSize() - off); |
| memcpy(static_cast<uint8_t *>(data), inline_data + off, cur_len); |
| |
| *out_actual = cur_len; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t File::ConvertInlineData() { |
| LockedPage page; |
| if (zx_status_t ret = GrabCachePage(0, &page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| LockedPage dnode_page; |
| if (zx_status_t err = Vfs()->GetNodeManager().GetLockedDnodePage(*this, 0, &dnode_page); |
| err != ZX_OK) { |
| return err; |
| } |
| |
| uint32_t ofs_in_dnode; |
| if (auto result = Vfs()->GetNodeManager().GetOfsInDnode(*this, 0); result.is_error()) { |
| return result.error_value(); |
| } else { |
| ofs_in_dnode = result.value(); |
| } |
| |
| NodePage *ipage = &dnode_page.GetPage<NodePage>(); |
| block_t data_blkaddr = DatablockAddr(ipage, ofs_in_dnode); |
| if (data_blkaddr == kNullAddr) { |
| if (zx_status_t err = ReserveNewBlock(*ipage, ofs_in_dnode); err != ZX_OK) { |
| return err; |
| } |
| } |
| |
| page->WaitOnWriteback(); |
| page->ZeroUserSegment(0, kPageSize); |
| |
| uint8_t *inline_data = InlineDataPtr(ipage); |
| memcpy(page->GetAddress(), inline_data, GetSize()); |
| |
| page->SetDirty(); |
| |
| ipage->WaitOnWriteback(); |
| ipage->ZeroUserSegment(InlineDataOffset(), InlineDataOffset() + MaxInlineData()); |
| // Clear regarding flags since we moved inline data to a data Page. |
| ipage->ClearMmapped(); |
| ClearFlag(InodeInfoFlag::kInlineData); |
| ClearFlag(InodeInfoFlag::kDataExist); |
| |
| UpdateInode(ipage); |
| |
| 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 = Vfs()->GetNodeManager().GetNodePage(Ino(), &inline_page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| inline_page->WaitOnWriteback(); |
| |
| uint8_t *inline_data = InlineDataPtr(inline_page.get()); |
| memcpy(inline_data + offset, static_cast<const uint8_t *>(data), len); |
| if (inline_page->IsMapped()) { |
| // Apply changes to its paged VMO. |
| ZX_ASSERT(WritePagedVmo(inline_data + offset, offset, len) == ZX_OK); |
| } |
| |
| SetSize(std::max(static_cast<size_t>(GetSize()), offset + len)); |
| SetFlag(InodeInfoFlag::kDataExist); |
| inline_page->SetDirty(); |
| |
| timespec cur_time; |
| clock_gettime(CLOCK_REALTIME, &cur_time); |
| SetCTime(cur_time); |
| SetMTime(cur_time); |
| MarkInodeDirty(); |
| |
| *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 = Vfs()->GetNodeManager().GetNodePage(Ino(), &inline_page); ret != ZX_OK) { |
| return ret; |
| } |
| |
| inline_page->WaitOnWriteback(); |
| |
| uint8_t *inline_data = InlineDataPtr(inline_page.get()); |
| size_t size_diff = (len > GetSize()) ? (len - GetSize()) : (GetSize() - len); |
| size_t offset = ((len > GetSize()) ? GetSize() : len); |
| memset(inline_data + offset, 0, size_diff); |
| if (inline_page->IsMapped()) { |
| // Apply changes to its paged VMO. |
| ZX_ASSERT(WritePagedVmo(inline_data + offset, offset, size_diff) == ZX_OK); |
| } |
| |
| // 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(); |
| } |
| timespec cur_time; |
| clock_gettime(CLOCK_REALTIME, &cur_time); |
| SetCTime(cur_time); |
| SetMTime(cur_time); |
| MarkInodeDirty(); |
| |
| 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 (TODO) |
| // x x -> recover data blocks |
| // ([prev.] is checkpointed data. And [next] is data written and fsynced after checkpoint.) |
| |
| Inode *raw_inode = nullptr; |
| if (IsInode(page)) { |
| raw_inode = &page.GetAddress<Node>()->i; |
| } |
| |
| // [next] have inline data. |
| if (raw_inode && (raw_inode->i_inline & kInlineData)) { |
| // TODO: We should consider converting data blocks to inline data. |
| |
| // Process inline. |
| LockedPage ipage; |
| if (zx_status_t err = Vfs()->GetNodeManager().GetNodePage(Ino(), &ipage); err != ZX_OK) { |
| return err; |
| } |
| ipage->WaitOnWriteback(); |
| memcpy(InlineDataPtr(ipage.get()), InlineDataPtr(&page), 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 |