blob: e2e4662373d8878c047fb116b40f44bb3ecd6646 [file] [log] [blame]
// 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) {
// 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 == 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::WriteInode(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
zx_status_t 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 ZX_OK;
}
/**
* 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->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) {
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