| // 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 <sys/stat.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include "f2fs.h" |
| |
| namespace f2fs { |
| |
| VnodeF2fs::VnodeF2fs(F2fs *fs) : fs_(fs) {} |
| |
| VnodeF2fs::VnodeF2fs(F2fs *fs, ino_t ino) : fs_(fs), ino_(ino) {} |
| |
| bool VnodeF2fs::IsDirectory() { return S_ISDIR(inode_.i_mode); } |
| |
| fs::VnodeProtocolSet VnodeF2fs::GetProtocols() const { |
| if (S_ISDIR(inode_.i_mode)) { |
| return fs::VnodeProtocol::kDirectory; |
| } else { |
| return fs::VnodeProtocol::kFile; |
| } |
| } |
| |
| zx_status_t VnodeF2fs::GetNodeInfoForProtocol([[maybe_unused]] fs::VnodeProtocol protocol, |
| [[maybe_unused]] fs::Rights rights, |
| fs::VnodeRepresentation *info) { |
| if (IsDirectory()) { |
| *info = fs::VnodeRepresentation::Directory(); |
| } else { |
| *info = fs::VnodeRepresentation::File(); |
| } |
| return ZX_OK; |
| } |
| |
| void VnodeF2fs::Allocate(F2fs *fs, ino_t ino, uint32_t mode, fbl::RefPtr<VnodeF2fs> *out) { |
| /* Check if ino is within scope */ |
| CheckNidRange(&fs->GetSbInfo(), ino); |
| |
| if (S_ISDIR(mode)) { |
| *out = fbl::AdoptRef(new Dir(fs, ino)); |
| } else { |
| *out = fbl::AdoptRef(new File(fs, ino)); |
| } |
| |
| VnodeF2fs *vnode = out->get(); |
| |
| mtx_init(&vnode->i_mutex_, mtx_plain); |
| |
| memset(&vnode->fi_, 0, sizeof(InodeInfo)); |
| |
| #if 0 // porting needed |
| // AtomicSet(&vnode->fi.vfs_inode.i_version, 1); |
| #endif |
| AtomicSet(&vnode->fi_.dirty_dents, 0); |
| vnode->fi_.i_current_depth = 1; |
| vnode->fi_.i_advise = 0; |
| RwlockInit(&vnode->fi_.ext.ext_lock); |
| |
| SetInodeFlag(&vnode->fi_, InodeInfoFlag::kFiNewInode); |
| } |
| |
| // TODO(sukka): fill vfs->members in addtion to size |
| // TODO(sukka): if dir/file vnode are defined as different class, check if the ino is for dir/file |
| void VnodeF2fs::Create(F2fs *fs, ino_t ino, fbl::RefPtr<VnodeF2fs> *out) { |
| Page *node_page = nullptr; |
| Inode *ri; |
| Node *rn; |
| |
| if (ino == NodeIno(&fs->GetSbInfo()) || ino == MetaIno(&fs->GetSbInfo())) { |
| *out = fbl::AdoptRef(new VnodeF2fs(fs, ino)); |
| return; |
| } |
| |
| /* Check if ino is within scope */ |
| CheckNidRange(&fs->GetSbInfo(), ino); |
| |
| if (fs->Nodemgr().GetNodePage(ino, &node_page) != ZX_OK) { |
| return; |
| } |
| |
| rn = static_cast<Node *>(PageAddress(node_page)); |
| ri = &(rn->i); |
| |
| // [sukka] need to check result? |
| if (S_ISDIR(ri->i_mode)) { |
| *out = fbl::AdoptRef(new Dir(fs, ino)); |
| } else { |
| *out = fbl::AdoptRef(new File(fs, ino)); |
| } |
| |
| memcpy(&(*out)->inode_, ri, sizeof(Inode)); |
| |
| VnodeF2fs *vnode = out->get(); |
| |
| mtx_init(&vnode->i_mutex_, mtx_plain); |
| |
| vnode->i_mode_ = LeToCpu(ri->i_mode); |
| vnode->i_uid_ = LeToCpu(ri->i_uid); |
| vnode->i_gid_ = LeToCpu(ri->i_gid); |
| vnode->i_nlink_ = ri->i_links; |
| vnode->i_size_ = LeToCpu(ri->i_size); |
| vnode->i_blocks_ = LeToCpu(ri->i_blocks); |
| |
| vnode->i_atime_.tv_sec = LeToCpu(ri->i_atime); |
| vnode->i_ctime_.tv_sec = LeToCpu(ri->i_ctime); |
| vnode->i_mtime_.tv_sec = LeToCpu(ri->i_mtime); |
| vnode->i_atime_.tv_nsec = LeToCpu(ri->i_atime_nsec); |
| vnode->i_ctime_.tv_nsec = LeToCpu(ri->i_ctime_nsec); |
| vnode->i_mtime_.tv_nsec = LeToCpu(ri->i_mtime_nsec); |
| vnode->i_generation_ = LeToCpu(ri->i_generation); |
| |
| vnode->fi_.i_current_depth = LeToCpu(ri->i_current_depth); |
| vnode->fi_.i_xattr_nid = LeToCpu(ri->i_xattr_nid); |
| vnode->fi_.i_flags = LeToCpu(ri->i_flags); |
| vnode->fi_.flags = 0; |
| #if 0 // porting needed |
| // vnode->fi.data_version = LeToCpu(GetCheckpoint(sbi)->checkpoint_ver) - 1; |
| #endif |
| vnode->fi_.i_advise = ri->i_advise; |
| RwlockInit(&vnode->fi_.ext.ext_lock); |
| GetExtentInfo(&vnode->fi_.ext, ri->i_ext); |
| |
| fbl::StringPiece name(reinterpret_cast<char *>(ri->i_name), ri->i_namelen); |
| vnode->i_name_sp_ = name; |
| |
| F2fsPutPage(node_page, 1); |
| } |
| |
| zx_status_t VnodeF2fs::Open([[maybe_unused]] ValidatedOptions options, |
| fbl::RefPtr<Vnode> *out_redirect) { |
| fd_count_++; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VnodeF2fs::Close() { |
| fd_count_--; |
| |
| return ZX_OK; |
| } |
| |
| void VnodeF2fs::fbl_recycle() { |
| fs_->EraseVnodeFromTable(this); |
| delete this; |
| } |
| |
| zx_status_t VnodeF2fs::GetAttributes(fs::VnodeAttributes *a) { |
| #ifdef F2FS_BU_DEBUG |
| FX_LOGS(DEBUG) << "f2fs_getattr() vn=" << this << "(#" << ino_ << ")"; |
| #endif |
| *a = fs::VnodeAttributes(); |
| |
| a->mode = i_mode_; |
| a->inode = ino_; |
| a->content_size = i_size_; |
| a->storage_size = i_blocks_ * kBlockSize; |
| a->link_count = i_nlink_; |
| a->creation_time = inode_.i_ctime; |
| a->modification_time = inode_.i_mtime; |
| |
| return ZX_OK; |
| } |
| |
| struct f2fs_iget_args { |
| uint64_t ino; |
| int on_free; |
| }; |
| |
| #if 0 // porting needed |
| // void VnodeF2fs::F2fsSetInodeFlags() { |
| // uint64_t &flags = fi.i_flags; |
| |
| // inode_.i_flags &= ~(S_SYNC | S_APPEND | S_IMMUTABLE | |
| // S_NOATIME | S_DIRSYNC); |
| |
| // if (flags & FS_SYNC_FL) |
| // inode_.i_flags |= S_SYNC; |
| // if (flags & FS_APPEND_FL) |
| // inode_.i_flags |= S_APPEND; |
| // if (flags & FS_IMMUTABLE_FL) |
| // inode_.i_flags |= S_IMMUTABLE; |
| // if (flags & FS_NOATIME_FL) |
| // inode_.i_flags |= S_NOATIME; |
| // if (flags & FS_DIRSYNC_FL) |
| // inode_.i_flags |= S_DIRSYNC; |
| // } |
| |
| // int VnodeF2fs::F2fsIgetTest(void *data) { |
| // f2fs_iget_args *args = (f2fs_iget_args *)data; |
| |
| // if (ino_ != args->ino) |
| // return 0; |
| // if (i_state & (I_FREEING | I_WILL_FREE)) { |
| // args->on_free = 1; |
| // return 0; |
| // } |
| // return 1; |
| // } |
| |
| // VnodeF2fs *VnodeF2fs::F2fsIgetNowait(uint64_t ino) { |
| // fbl::RefPtr<VnodeF2fs> vnode_refptr; |
| // VnodeF2fs *vnode = nullptr; |
| // f2fs_iget_args args = {.ino = ino, .on_free = 0}; |
| // vnode = ilookup5(sb, ino, F2fsIgetTest, &args); |
| |
| // if (vnode) |
| // return vnode; |
| // if (!args.on_free) { |
| // Vget(Vfs(), ino, &vnode_refptr); |
| // vnode = vnode_refptr.get(); |
| // return vnode; |
| // } |
| // return static_cast<VnodeF2fs *>(ErrPtr(ZX_ERR_NOT_FOUND)); |
| // } |
| #endif |
| |
| zx_status_t VnodeF2fs::Vget(F2fs *fs, uint64_t ino, fbl::RefPtr<VnodeF2fs> *out) { |
| fbl::RefPtr<VnodeF2fs> vnode_refptr; |
| VnodeF2fs *vnode; |
| |
| if (fs->FindVnode(&vnode_refptr, ino) == ZX_OK) { |
| *out = std::move(vnode_refptr); |
| return ZX_OK; |
| } |
| |
| Create(fs, ino, &vnode_refptr); |
| |
| vnode = vnode_refptr.get(); |
| |
| if (vnode == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| fs->InsertVnode(vnode); |
| |
| fbl::AutoLock lock(&vnode->v_lock_); |
| |
| #if 0 // porting needed |
| // if (!(vnode->i_state & I_NEW)) |
| // return vnode; |
| #endif |
| |
| if (!(ino == NodeIno(&fs->GetSbInfo()) || ino == MetaIno(&fs->GetSbInfo()))) { |
| if (!fs->GetSbInfo().por_doing && vnode->i_nlink_ == 0) { |
| #if 0 // porting needed |
| // iget_failed(inode); |
| #endif |
| return ZX_ERR_NOT_FOUND; |
| } |
| } |
| |
| #if 0 // porting needed |
| // if (ino == NodeIno(sbi)) { |
| // // inode->i_mapping->a_ops = &Node_aops; //invalidatepage, releasepage |
| // // mapping_set_gfp_mask(inode->i_mapping, GFP_F2FS_ZERO); |
| // } else if (ino == MetaIno(sbi)) { |
| // // inode->i_mapping->a_ops = &f2fs_meta_aops; //empty |
| // // mapping_set_gfp_mask(inode->i_mapping, GFP_F2FS_ZERO); |
| // } else if (S_ISREG(inode->i_mode)) { |
| // // inode->i_op = &f2fs_file_inode_operations; //empty |
| // // inode->i_fop = &f2fs_file_operations; //empty |
| // // inode->i_mapping->a_ops = &f2fs_dblock_aops; |
| // } else if (S_ISDIR(inode->i_mode)) { |
| // // inode->i_op = &f2fs_dir_inode_operations; //lookup only |
| // // inode->i_fop = &f2fs_dir_operations; //read, readdir |
| // // inode->i_mapping->a_ops = &f2fs_dblock_aops; //readpage, readpages, invalidatepage, |
| // releasepage |
| // // mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER_MOVABLE | |
| // // __GFP_ZERO); |
| // } else if (S_ISLNK(inode->i_mode)) { |
| // // inode->i_op = &f2fs_symlink_inode_operations; //empty |
| // // inode->i_mapping->a_ops = &f2fs_dblock_aops; |
| // } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || |
| // S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { |
| // // inode->i_op = &f2fs_special_inode_operations; //empty |
| // init_special_inode(inode, inode->i_mode, inode->i_rdev); |
| // } else { |
| // iget_failed(inode); |
| // return ZX_ERR_IO; |
| // } |
| #endif |
| |
| *out = std::move(vnode_refptr); |
| |
| return ZX_OK; |
| } |
| |
| void VnodeF2fs::UpdateInode(Page *node_page) { |
| Node *rn; |
| Inode *ri; |
| |
| WaitOnPageWriteback(node_page); |
| |
| rn = static_cast<Node *>(PageAddress(node_page)); |
| ri = &(rn->i); |
| |
| ri->i_mode = CpuToLe(i_mode_); |
| ri->i_advise = fi_.i_advise; |
| ri->i_uid = CpuToLe(i_uid_); |
| ri->i_gid = CpuToLe(i_gid_); |
| ri->i_links = CpuToLe(i_nlink_); |
| ri->i_size = CpuToLe(static_cast<uint64_t>(i_size_)); |
| ri->i_blocks = CpuToLe(static_cast<uint64_t>(i_blocks_)); |
| set_raw_extent(&fi_.ext, &ri->i_ext); |
| |
| ri->i_atime = CpuToLe(static_cast<uint64_t>(i_atime_.tv_sec)); |
| ri->i_ctime = CpuToLe(static_cast<uint64_t>(i_ctime_.tv_sec)); |
| ri->i_mtime = CpuToLe(static_cast<uint64_t>(i_mtime_.tv_sec)); |
| ri->i_atime_nsec = CpuToLe(static_cast<uint32_t>(i_atime_.tv_nsec)); |
| ri->i_ctime_nsec = CpuToLe(static_cast<uint32_t>(i_ctime_.tv_nsec)); |
| ri->i_mtime_nsec = CpuToLe(static_cast<uint64_t>(i_mtime_.tv_nsec)); |
| ri->i_current_depth = CpuToLe(fi_.i_current_depth); |
| ri->i_xattr_nid = CpuToLe(fi_.i_xattr_nid); |
| ri->i_flags = CpuToLe(fi_.i_flags); |
| ri->i_generation = CpuToLe(i_generation_); |
| #if 0 // porting needed |
| // set_page_dirty(node_page); |
| #else |
| FlushDirtyNodePage(Vfs(), node_page); |
| #endif |
| } |
| |
| int VnodeF2fs::WriteInode(WritebackControl *wbc) TA_NO_THREAD_SAFETY_ANALYSIS { |
| SbInfo &sbi = Vfs()->GetSbInfo(); |
| Page *node_page = nullptr; |
| zx_status_t ret = ZX_OK; |
| |
| if (ino_ == NodeIno(&sbi) || ino_ == MetaIno(&sbi)) |
| return ret; |
| |
| if (ret = Vfs()->Nodemgr().GetNodePage(ino_, &node_page); ret != ZX_OK) |
| return ret; |
| |
| if (PageDirty(node_page)) { |
| UpdateInode(node_page); |
| F2fsPutPage(node_page, 1); |
| } else { |
| F2fsPutPage(node_page, 1); |
| fbl::AutoLock lock(&sbi.write_inode); |
| if (ret = Vfs()->Nodemgr().GetNodePage(ino_, &node_page); ret != ZX_OK) |
| return ret; |
| UpdateInode(node_page); |
| F2fsPutPage(node_page, 1); |
| } |
| |
| return ZX_OK; |
| } |
| |
| #if 0 // porting needed |
| // void VnodeF2fs::F2fsTruncate() { |
| // if (!(S_ISREG(i_mode_) || S_ISDIR(i_mode_) || S_ISLNK(i_mode_))) |
| // return; |
| |
| // if (!TruncateBlocks(i_size)) { |
| // auto cur_time = time(nullptr); |
| // i_mtime.tv_sec = cur_time; |
| // i_mtime.tv_nsec = 0; |
| // i_ctime.tv_sec = cur_time; |
| // i_ctime.tv_nsec = 0; |
| // MarkInodeDirty(this); |
| // } |
| |
| // Vfs()->Segmgr().BalanceFs(); |
| // } |
| #endif |
| |
| int VnodeF2fs::TruncateDataBlocksRange(DnodeOfData *dn, int count) { |
| int nr_free = 0, ofs = dn->ofs_in_node; |
| SbInfo &sbi = Vfs()->GetSbInfo(); |
| Node *raw_node; |
| uint32_t *addr; |
| |
| raw_node = static_cast<Node *>(PageAddress(dn->node_page)); |
| addr = BlkaddrInNode(raw_node) + ofs; |
| |
| for (; count > 0; count--, addr++, dn->ofs_in_node++) { |
| block_t blkaddr = LeToCpu(*addr); |
| if (blkaddr == kNullAddr) |
| continue; |
| |
| UpdateExtentCache(kNullAddr, dn); |
| Vfs()->Segmgr().InvalidateBlocks(blkaddr); |
| DecValidBlockCount(&sbi, dn->vnode, 1); |
| nr_free++; |
| } |
| if (nr_free) { |
| #if 0 // porting needed |
| // set_page_dirty(dn->node_page); |
| #else |
| FlushDirtyNodePage(Vfs(), dn->node_page); |
| #endif |
| Vfs()->Nodemgr().SyncInodePage(dn); |
| } |
| dn->ofs_in_node = ofs; |
| return nr_free; |
| } |
| |
| void VnodeF2fs::TruncateDataBlocks(DnodeOfData *dn) { |
| TruncateDataBlocksRange(dn, kAddrsPerBlock); |
| } |
| |
| void VnodeF2fs::TruncatePartialDataPage(uint64_t from) { |
| unsigned offset = from & (kPageCacheSize - 1); |
| Page *page = nullptr; |
| |
| if (!offset) |
| return; |
| |
| if (FindDataPage(from >> kPageCacheShift, &page) != ZX_OK) |
| return; |
| |
| #if 0 // porting needed |
| // lock_page(page); |
| #endif |
| WaitOnPageWriteback(page); |
| zero_user(page, offset, kPageCacheSize - offset); |
| #if 0 // porting needed |
| // set_page_dirty(page); |
| #else |
| FlushDirtyDataPage(Vfs(), page); |
| #endif |
| F2fsPutPage(page, 1); |
| } |
| |
| #if 0 // porting needed |
| // int VnodeF2fs::TruncateBlocks(uint64_t from) { |
| // SbInfo &sbi = Vfs()->GetSbInfo(); |
| // unsigned int blocksize = sbi.blocksize; |
| // DnodeOfData dn; |
| // pgoff_t free_from; |
| // int count = 0; |
| // int err; |
| |
| // free_from = (pgoff_t)((from + blocksize - 1) >> (sbi.log_blocksize)); |
| |
| // mutex_lock_op(&sbi, LockType::kDataTrunc); |
| |
| // SetNewDnode(&dn, this, NULL, NULL, 0); |
| // err = Vfs()->Nodemgr().GetDnodeOfData(&dn, free_from, kRdOnlyNode); |
| // if (err) { |
| // if (err == ZX_ERR_NOT_FOUND) |
| // goto free_next; |
| // mutex_unlock_op(&sbi, LockType::kDataTrunc); |
| // return err; |
| // } |
| |
| // if (IsInode(dn.node_page)) |
| // count = KAddrsPerInode; |
| // else |
| // count = kAddrsPerBlock; |
| |
| // count -= dn.ofs_in_node; |
| // BUG_ON(count < 0); |
| // if (dn.ofs_in_node || IsInode(dn.node_page)) { |
| // TruncateDataBlocksRange(&dn, count); |
| // free_from += count; |
| // } |
| |
| // F2fsPutDnode(&dn); |
| // free_next: |
| // err = Vfs()->Nodemgr().TruncateInodeBlocks(this, free_from); |
| // mutex_unlock_op(&sbi, LockType::kDataTrunc); |
| |
| // /* lastly zero out the first data page */ |
| // TruncatePartialDataPage(from); |
| |
| // return err; |
| // } |
| #endif |
| |
| zx_status_t VnodeF2fs::TruncateHole(pgoff_t pg_start, pgoff_t pg_end) { |
| pgoff_t index; |
| |
| for (index = pg_start; index < pg_end; index++) { |
| DnodeOfData dn; |
| SbInfo &sbi = Vfs()->GetSbInfo(); |
| |
| fbl::AutoLock lock(&sbi.fs_lock[static_cast<int>(LockType::kDataTrunc)]); |
| SetNewDnode(&dn, this, NULL, NULL, 0); |
| if (zx_status_t err = Vfs()->Nodemgr().GetDnodeOfData(&dn, index, kRdOnlyNode); err != ZX_OK) { |
| if (err == ZX_ERR_NOT_FOUND) |
| continue; |
| return err; |
| } |
| |
| if (dn.data_blkaddr != kNullAddr) |
| TruncateDataBlocksRange(&dn, 1); |
| F2fsPutDnode(&dn); |
| } |
| return ZX_OK; |
| } |
| |
| /** |
| * Called at the last Iput() if i_nlink is zero |
| */ |
| #if 0 // porting needed |
| // void VnodeF2fs::F2fsEvictInode() { |
| // SbInfo &sbi = Vfs()->GetSbInfo(); |
| |
| // // truncate_inode_pages(&inode->i_data, 0); |
| |
| // if (ino_ == NodeIno(&sbi) || ino_ == MetaIno(&sbi)) |
| // goto no_delete; |
| |
| // // BUG_ON(AtomicRead(&fi.->dirty_dents)); |
| // // remove_dirty_dir_inode(this); |
| |
| // if (i_nlink || IsBadInode(this)) |
| // goto no_delete; |
| |
| // SetInodeFlag(&fi, InodeInfoFlag::kFiNoAlloc); |
| // i_size = 0; |
| |
| // if (HasBlocks(this)) |
| // F2fsTruncate(); |
| |
| // // remove_inode_page(inode); |
| // no_delete: |
| // ClearInode(this); |
| // } |
| #endif |
| |
| void VnodeF2fs::IncNlink() { i_nlink_++; } |
| |
| void VnodeF2fs::DropNlink() { i_nlink_--; } |
| |
| void VnodeF2fs::ClearNlink() { i_nlink_ = 0; } |
| |
| void VnodeF2fs::SetNlink(uint32_t nlink) { i_nlink_ = nlink; } |
| |
| void MarkInodeDirty(VnodeF2fs *vnode) { vnode->WriteInode(nullptr); } |
| |
| void VnodeF2fs::Sync(SyncCallback closure) { |
| File *file; |
| |
| if (closure) { |
| if (S_ISDIR(this->i_mode_)) { |
| std::cout << "Sync: Invalid args(fsync to directory)" << std::endl; |
| closure(ZX_ERR_INVALID_ARGS); |
| } else { |
| file = static_cast<File *>(this); |
| file->SyncFile(0, file->i_size_, 1); |
| closure(ZX_OK); |
| } |
| } |
| } |
| |
| zx_status_t VnodeF2fs::QueryFilesystem(::fuchsia_io::wire::FilesystemInfo *info) { |
| SbInfo &sbi = Vfs()->GetSbInfo(); |
| *info = {}; |
| info->block_size = kBlockSize; |
| info->max_filename_size = kMaxNameLen; |
| info->fs_type = VFS_TYPE_F2FS; |
| info->fs_id = Vfs()->FsId(); |
| info->total_bytes = sbi.user_block_count * kBlockSize; |
| info->used_bytes = ValidUserBlocks(&sbi) * kBlockSize; |
| info->total_nodes = sbi.total_node_count; |
| info->used_nodes = sbi.total_valid_inode_count; |
| |
| constexpr std::string_view kFsName = "f2fs"; |
| static_assert(kFsName.size() + 1 < ::fuchsia_io::wire::MAX_FS_NAME_BUFFER, "F2fs name too long"); |
| info->name[kFsName.copy(reinterpret_cast<char *>(info->name.data()), |
| ::fuchsia_io::wire::MAX_FS_NAME_BUFFER - 1)] = '\0'; |
| |
| // TODO(unknown): Fill info->free_shared_pool_bytes using fvm info |
| |
| return ZX_OK; |
| } |
| |
| } // namespace f2fs |