| // 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->SbInfo(), 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(f2fs_inode_info)); |
| |
| #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, FI_NEW_INODE); |
| } |
| |
| // 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; |
| f2fs_inode *ri; |
| f2fs_node *rn; |
| |
| if (ino == F2FS_NODE_INO(&fs->SbInfo()) || ino == F2FS_META_INO(&fs->SbInfo())) { |
| *out = fbl::AdoptRef(new VnodeF2fs(fs, ino)); |
| return; |
| } |
| |
| /* Check if ino is within scope */ |
| CheckNidRange(&fs->SbInfo(), ino); |
| |
| if (fs->Nodemgr().GetNodePage(ino, &node_page) != ZX_OK) { |
| return; |
| } |
| |
| rn = static_cast<f2fs_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(f2fs_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(F2FS_CKPT(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 * kF2fsBlockSize; |
| 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) { |
| // F2fsVget(Vfs(), ino, &vnode_refptr); |
| // vnode = vnode_refptr.get(); |
| // return vnode; |
| // } |
| // return static_cast<VnodeF2fs *>(ErrPtr(ZX_ERR_NOT_FOUND)); |
| // } |
| #endif |
| |
| zx_status_t VnodeF2fs::F2fsVget(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 == F2FS_NODE_INO(&fs->SbInfo()) || ino == F2FS_META_INO(&fs->SbInfo()))) { |
| if (!fs->SbInfo().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 == F2FS_NODE_INO(sbi)) { |
| // // inode->i_mapping->a_ops = &f2fs_node_aops; //invalidatepage, releasepage |
| // // mapping_set_gfp_mask(inode->i_mapping, GFP_F2FS_ZERO); |
| // } else if (ino == F2FS_META_INO(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) { |
| f2fs_node *rn; |
| f2fs_inode *ri; |
| |
| WaitOnPageWriteback(node_page); |
| |
| rn = static_cast<f2fs_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::F2fsWriteInode(WritebackControl *wbc) TA_NO_THREAD_SAFETY_ANALYSIS { |
| f2fs_sb_info &sbi = Vfs()->SbInfo(); |
| Page *node_page = nullptr; |
| zx_status_t ret = ZX_OK; |
| |
| if (ino_ == F2FS_NODE_INO(&sbi) || ino_ == F2FS_META_INO(&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().F2fsBalanceFs(); |
| // } |
| #endif |
| |
| int VnodeF2fs::TruncateDataBlocksRange(dnode_of_data *dn, int count) { |
| int nr_free = 0, ofs = dn->ofs_in_node; |
| f2fs_sb_info &sbi = Vfs()->SbInfo(); |
| f2fs_node *raw_node; |
| uint32_t *addr; |
| |
| raw_node = static_cast<f2fs_node *>(PageAddress(dn->node_page)); |
| addr = blkaddr_in_node(raw_node) + ofs; |
| |
| for (; count > 0; count--, addr++, dn->ofs_in_node++) { |
| block_t blkaddr = LeToCpu(*addr); |
| if (blkaddr == NULL_ADDR) |
| continue; |
| |
| UpdateExtentCache(NULL_ADDR, dn); |
| Vfs()->Segmgr().InvalidateBlocks(blkaddr); |
| dec_valid_block_count(&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(dnode_of_data *dn) { |
| TruncateDataBlocksRange(dn, ADDRS_PER_BLOCK); |
| } |
| |
| 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) { |
| // f2fs_sb_info &sbi = Vfs()->SbInfo(); |
| // unsigned int blocksize = sbi.blocksize; |
| // dnode_of_data dn; |
| // pgoff_t free_from; |
| // int count = 0; |
| // int err; |
| |
| // free_from = (pgoff_t)((from + blocksize - 1) >> (sbi.log_blocksize)); |
| |
| // mutex_lock_op(&sbi, DATA_TRUNC); |
| |
| // SetNewDnode(&dn, this, NULL, NULL, 0); |
| // err = Vfs()->Nodemgr().GetDnodeOfData(&dn, free_from, RDONLY_NODE); |
| // if (err) { |
| // if (err == ZX_ERR_NOT_FOUND) |
| // goto free_next; |
| // mutex_unlock_op(&sbi, DATA_TRUNC); |
| // return err; |
| // } |
| |
| // if (IS_INODE(dn.node_page)) |
| // count = ADDRS_PER_INODE; |
| // else |
| // count = ADDRS_PER_BLOCK; |
| |
| // count -= dn.ofs_in_node; |
| // BUG_ON(count < 0); |
| // if (dn.ofs_in_node || IS_INODE(dn.node_page)) { |
| // TruncateDataBlocksRange(&dn, count); |
| // free_from += count; |
| // } |
| |
| // F2fsPutDnode(&dn); |
| // free_next: |
| // err = Vfs()->Nodemgr().TruncateInodeBlocks(this, free_from); |
| // mutex_unlock_op(&sbi, DATA_TRUNC); |
| |
| // /* lastly zero out the first data page */ |
| // TruncatePartialDataPage(from); |
| |
| // return err; |
| // } |
| #endif |
| |
| int VnodeF2fs::TruncateHole(pgoff_t pg_start, pgoff_t pg_end) { |
| pgoff_t index; |
| |
| for (index = pg_start; index < pg_end; index++) { |
| dnode_of_data dn; |
| f2fs_sb_info &sbi = Vfs()->SbInfo(); |
| |
| fbl::AutoLock lock(&sbi.fs_lock[DATA_TRUNC]); |
| SetNewDnode(&dn, this, NULL, NULL, 0); |
| if (zx_status_t err = Vfs()->Nodemgr().GetDnodeOfData(&dn, index, RDONLY_NODE); err != ZX_OK) { |
| if (err == ZX_ERR_NOT_FOUND) |
| continue; |
| return err; |
| } |
| |
| if (dn.data_blkaddr != NULL_ADDR) |
| TruncateDataBlocksRange(&dn, 1); |
| F2fsPutDnode(&dn); |
| } |
| return 0; |
| } |
| |
| /** |
| * Called at the last Iput() if i_nlink is zero |
| */ |
| #if 0 // porting needed |
| // void VnodeF2fs::F2fsEvictInode() { |
| // f2fs_sb_info &sbi = Vfs()->SbInfo(); |
| |
| // // truncate_inode_pages(&inode->i_data, 0); |
| |
| // if (ino_ == F2FS_NODE_INO(&sbi) || ino_ == F2FS_META_INO(&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, FI_NO_ALLOC); |
| // i_size = 0; |
| |
| // if (F2FS_HAS_BLOCKS(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->F2fsWriteInode(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->F2fsSyncFile(0, file->i_size, 1); |
| closure(ZX_OK); |
| } |
| } |
| } |
| |
| zx_status_t VnodeF2fs::QueryFilesystem(::fuchsia_io::wire::FilesystemInfo *info) { |
| f2fs_sb_info &sbi = Vfs()->SbInfo(); |
| *info = {}; |
| info->block_size = kF2fsBlockSize; |
| info->max_filename_size = F2FS_MAX_NAME_LEN; |
| info->fs_type = VFS_TYPE_F2FS; |
| info->fs_id = Vfs()->FsId(); |
| info->total_bytes = sbi.user_block_count * kF2fsBlockSize; |
| info->used_bytes = valid_user_blocks(&sbi) * kF2fsBlockSize; |
| 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 |