| // 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 <sys/stat.h> |
| |
| #include <algorithm> |
| |
| #include "f2fs.h" |
| |
| namespace f2fs { |
| |
| File::File(F2fs *fs) : VnodeF2fs(fs) {} |
| |
| File::File(F2fs *fs, ino_t ino) : VnodeF2fs(fs, ino) {} |
| |
| #if 0 // porting needed |
| // int File::F2fsVmPageMkwrite(vm_area_struct *vma, vm_fault *vmf) { |
| // return 0; |
| // // Page *page = vmf->page; |
| // // VnodeF2fs *vnode = this; |
| // // // SbInfo &sbi = Vfs()->GetSbInfo(); |
| // // Page *node_page; |
| // // block_t old_blk_addr; |
| // // DnodeOfData dn; |
| // // int err; |
| |
| // // Vfs()->Segmgr().BalanceFs(); |
| |
| // // sb_start_pagefault(nullptr); |
| |
| // // // mutex_lock_op(sbi, LockType::kDataNew); |
| |
| // // /* block allocation */ |
| // // SetNewDnode(&dn, vnode, NULL, NULL, 0); |
| // // err = Vfs()->Nodemgr().GetDnodeOfData(&dn, page->index, 0); |
| // // if (err) { |
| // // // mutex_unlock_op(sbi, LockType::kDataNew); |
| // // goto out; |
| // // } |
| |
| // // old_blk_addr = dn.data_blkaddr; |
| // // node_page = dn.node_page; |
| |
| // // if (old_blk_addr == kNullAddr) { |
| // // err = VnodeF2fs::ReserveNewBlock(&dn); |
| // // if (err) { |
| // // F2fsPutDnode(&dn); |
| // // // mutex_unlock_op(sbi, LockType::kDataNew); |
| // // goto out; |
| // // } |
| // // } |
| // // F2fsPutDnode(&dn); |
| |
| // // // mutex_unlock_op(sbi, LockType::kDataNew); |
| |
| // // // lock_page(page); |
| // // // if (page->mapping != inode->i_mapping || |
| // // // page_offset(page) >= i_size_read(inode) || |
| // // // !PageUptodate(page)) { |
| // // // unlock_page(page); |
| // // // err = -EFAULT; |
| // // // goto out; |
| // // // } |
| |
| // // /* |
| // // * check to see if the page is mapped already (no holes) |
| // // */ |
| // // if (PageMappedToDisk(page)) |
| // // goto out; |
| |
| // // /* fill the page */ |
| // // WaitOnPageWriteback(page); |
| |
| // // /* page is wholly or partially inside EOF */ |
| // // if (((page->index + 1) << kPageCacheShift) > i_size) { |
| // // unsigned offset; |
| // // offset = i_size & ~PAGE_CACHE_MASK; |
| // // ZeroUserSegment(page, offset, kPageCacheSize); |
| // // } |
| // // // set_page_dirty(page); |
| // // FlushDirtyDataPage(Vfs(), page); |
| // // SetPageUptodate(page); |
| |
| // // file_update_time(vma->vm_file); |
| // // out: |
| // // sb_end_pagefault(nullptr); |
| // // return block_page_mkwrite_return(err); |
| // } |
| #endif |
| |
| int File::NeedToSyncDir(SbInfo *sbi, VnodeF2fs *vnode) { |
| #if 0 // porting needed |
| // dentry *dentry; |
| #endif |
| nid_t pino = 0; |
| |
| vnode = static_cast<VnodeF2fs *>(Igrab((void *)vnode)); |
| #if 0 // porting needed |
| // dentry = d_find_any_alias(vnode); |
| // if (!dentry) { |
| // // Iput(inode); |
| // return 0; |
| // } |
| // pino = dentry->d_parent->d_inode->i_ino; |
| // dput(dentry); |
| #endif |
| Iput(vnode); |
| return !Vfs()->Nodemgr().IsCheckpointedNode(pino); |
| } |
| |
| zx_status_t File::SyncFile(loff_t start, loff_t end, int datasync) { |
| SbInfo &sbi = Vfs()->GetSbInfo(); |
| uint64_t cur_version; |
| zx_status_t ret = 0; |
| bool need_cp = false; |
| #if 0 // porting needed |
| // WritebackControl wbc; |
| |
| // wbc.sync_mode = WB_SYNC_ALL; |
| // wbc.nr_to_write = LONG_MAX; |
| // wbc.for_reclaim = 0; |
| |
| // ret = filemap_write_and_wait_range(inode->i_mapping, start, end); |
| // if (ret) |
| // return ret; |
| #endif |
| |
| fbl::AutoLock lock(&i_mutex_); |
| |
| #if 0 // porting needed |
| // if (inode->i_sb->s_flags & MS_RDONLY) |
| // goto out; |
| // if (datasync && !(i_state & I_DIRTY_DATASYNC)) { |
| // goto out; |
| //} |
| #endif |
| |
| do { |
| fbl::AutoLock cplock(&sbi.cp_mutex); |
| cur_version = LeToCpu(GetCheckpoint(&sbi)->checkpoint_ver); |
| } while (false); |
| |
| #if 0 // porting needed |
| // if (fi.data_version != cur_version && |
| // !(i_state & I_DIRTY)) { |
| // goto out; |
| //} |
| #endif |
| fi_.data_version--; |
| |
| if (!S_ISREG(i_mode_) || i_nlink_ != 1) |
| need_cp = true; |
| if (IsInodeFlagSet(&fi_, InodeInfoFlag::kNeedCp)) |
| need_cp = true; |
| if (!Vfs()->SpaceForRollForward()) |
| need_cp = true; |
| if (NeedToSyncDir(&sbi, this)) |
| need_cp = true; |
| |
| #if 0 // porting needed |
| // WriteInode(nullptr); |
| #endif |
| |
| // clang-format off |
| // TODO(unknown): fsync recovery |
| #if 0 // porting needed |
| // if (need_cp) { |
| #else |
| if (true) { |
| #endif |
| WriteInode(nullptr); |
| /* all the dirty node pages should be flushed for POR */ |
| ret = Vfs()->SyncFs(1); |
| ClearInodeFlag(&fi_, InodeInfoFlag::kNeedCp); |
| } |
| else { |
| Page *node_page = nullptr; |
| int mark = !Vfs()->Nodemgr().IsCheckpointedNode(Ino()); |
| if (ret = Vfs()->Nodemgr().GetNodePage(Ino(), &node_page); ret != ZX_OK) { |
| return ret; |
| } |
| // clang-format on |
| |
| Vfs()->Nodemgr().SetFsyncMark(node_page, 1); |
| Vfs()->Nodemgr().SetDentryMark(node_page, mark); |
| |
| UpdateInode(node_page); |
| F2fsPutPage(node_page, 1); |
| |
| #if 0 // porting needed |
| // while (Vfs()->Nodemgr().SyncNodePages(Ino(), &wbc) == 0) |
| // WriteInode(nullptr); |
| // filemap_fdatawait_range(nullptr,//sbi->node_inode->i_mapping, |
| // 0, LONG_MAX); |
| #endif |
| } |
| #if 0 // porting needed |
| // out: |
| #endif |
| return ret; |
| } |
| |
| #if 0 // porting needed |
| // int File::F2fsFileMmap(/*file *file,*/ vm_area_struct *vma) { |
| // // file_accessed(file); |
| // // vma->vm_ops = &f2fs_file_vm_ops; |
| // return 0; |
| // } |
| |
| // void File::FillZero(pgoff_t index, loff_t start, loff_t len) { |
| // Page *page = nullptr; |
| // zx_status_t err; |
| |
| // if (!len) |
| // return; |
| |
| // err = GetNewDataPage(index, false, page); |
| |
| // if (!err) { |
| // WaitOnPageWriteback(page); |
| // zero_user(page, start, len); |
| // #if 0 // porting needed |
| // // set_page_dirty(page); |
| // #else |
| // FlushDirtyDataPage(Vfs(), page); |
| // #endif |
| // F2fsPutPage(page, 1); |
| // } |
| // } |
| |
| // int File::PunchHole(loff_t offset, loff_t len, int mode) { |
| // pgoff_t pg_start, pg_end; |
| // loff_t off_start, off_end; |
| // int ret = 0; |
| |
| // pg_start = ((uint64_t)offset) >> kPageCacheShift; |
| // pg_end = ((uint64_t)offset + len) >> kPageCacheShift; |
| |
| // off_start = offset & (kPageCacheSize - 1); |
| // off_end = (offset + len) & (kPageCacheSize - 1); |
| |
| // if (pg_start == pg_end) { |
| // FillZero(pg_start, off_start, off_end - off_start); |
| // } else { |
| // if (off_start) |
| // FillZero(pg_start++, off_start, kPageCacheSize - off_start); |
| // if (off_end) |
| // FillZero(pg_end, 0, off_end); |
| |
| // if (pg_start < pg_end) { |
| // loff_t blk_start, blk_end; |
| |
| // blk_start = pg_start << kPageCacheShift; |
| // blk_end = pg_end << kPageCacheShift; |
| // #if 0 // porting needed |
| // // truncate_inode_pages_range(nullptr, blk_start, |
| // // blk_end - 1); |
| // #endif |
| // ret = TruncateHole(pg_start, pg_end); |
| // } |
| // } |
| |
| // if (!(mode & FALLOC_FL_KEEP_SIZE) && i_size <= (offset + len)) { |
| // i_size = offset; |
| // MarkInodeDirty(this); |
| // } |
| |
| // return ret; |
| // } |
| |
| // int File::ExpandInodeData(loff_t offset, off_t len, int mode) { |
| // SbInfo &sbi = Vfs()->GetSbInfo(); |
| // pgoff_t index, pg_start, pg_end; |
| // loff_t new_size = i_size; |
| // loff_t off_start, off_end; |
| // int ret = 0; |
| |
| // #if 0 // porting needed |
| // // ret = inode_newsize_ok(this, (len + offset)); |
| // // if (ret) |
| // // return ret; |
| // #endif |
| |
| // pg_start = ((uint64_t)offset) >> kPageCacheShift; |
| // pg_end = ((uint64_t)offset + len) >> kPageCacheShift; |
| |
| // off_start = offset & (kPageCacheSize - 1); |
| // off_end = (offset + len) & (kPageCacheSize - 1); |
| |
| // for (index = pg_start; index <= pg_end; index++) { |
| // DnodeOfData dn; |
| |
| // mutex_lock_op(&sbi, LockType::kDataNew); |
| |
| // SetNewDnode(&dn, this, NULL, NULL, 0); |
| // ret = Vfs()->Nodemgr().GetDnodeOfData(&dn, index, 0); |
| // if (ret) { |
| // mutex_unlock_op(&sbi, LockType::kDataNew); |
| // break; |
| // } |
| |
| // if (dn.data_blkaddr == kNullAddr) { |
| // ret = ReserveNewBlock(&dn); |
| // if (ret) { |
| // F2fsPutDnode(&dn); |
| // mutex_unlock_op(&sbi, LockType::kDataNew); |
| // break; |
| // } |
| // } |
| // F2fsPutDnode(&dn); |
| |
| // mutex_unlock_op(&sbi, LockType::kDataNew); |
| |
| // if (pg_start == pg_end) |
| // new_size = offset + len; |
| // else if (index == pg_start && off_start) |
| // new_size = (index + 1) << kPageCacheShift; |
| // else if (index == pg_end) |
| // new_size = (index << kPageCacheShift) + off_end; |
| // else |
| // new_size += kPageCacheSize; |
| // } |
| |
| // if (!(mode & FALLOC_FL_KEEP_SIZE) && i_size < new_size) { |
| // i_size = new_size; |
| // MarkInodeDirty(this); |
| // } |
| |
| // return ret; |
| // } |
| |
| // long File::F2fsFallocate(int mode, loff_t offset, loff_t len) { |
| // long ret; |
| |
| // if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) |
| // return -EOPNOTSUPP; |
| |
| // if (mode & FALLOC_FL_PUNCH_HOLE) |
| // ret = PunchHole(offset, len, mode); |
| // else |
| // ret = ExpandInodeData(offset, len, mode); |
| |
| // Vfs()->Segmgr().BalanceFs(); |
| // return ret; |
| // } |
| |
| // #define F2FS_REG_FLMASK (~(FS_DIRSYNC_FL | FS_TOPDIR_FL)) |
| // #define F2FS_OTHER_FLMASK (FS_NODUMP_FL | FS_NOATIME_FL) |
| |
| // inline uint32_t File::F2fsMaskFlags(umode_t mode, uint32_t flags) { |
| // if (S_ISDIR(mode)) |
| // return flags; |
| // else if (S_ISREG(mode)) |
| // return flags & F2FS_REG_FLMASK; |
| // else |
| // return flags & F2FS_OTHER_FLMASK; |
| // } |
| |
| // long File::F2fsIoctl(/*file *filp,*/ unsigned int cmd, uint64_t arg) { |
| // inode *inode = filp->f_dentry->d_inode; |
| // InodeInfo *fi = F2FS_I(inode); |
| // unsigned int flags; |
| // int ret; |
| |
| // switch (cmd) { |
| // case FS_IOC_GETFLAGS: |
| // flags = fi->i_flags & FS_FL_USER_VISIBLE; |
| // return put_user(flags, (int __user *) arg); |
| // case FS_IOC_SETFLAGS: |
| // { |
| // unsigned int oldflags; |
| |
| // ret = mnt_want_write(filp->f_path.mnt); |
| // if (ret) |
| // return ret; |
| |
| // if (!inode_owner_or_capable(inode)) { |
| // ret = -EACCES; |
| // goto out; |
| // } |
| |
| // if (get_user(flags, (int __user *) arg)) { |
| // ret = -EFAULT; |
| // goto out; |
| // } |
| |
| // flags = f2fs_mask_flags(inode->i_mode, flags); |
| |
| // mutex_lock(&inode->i_mutex_); |
| |
| // oldflags = fi->i_flags; |
| |
| // if ((flags ^ oldflags) & (FS_APPEND_FL | FS_IMMUTABLE_FL)) { |
| // if (!capable(CAP_LINUX_IMMUTABLE)) { |
| // mutex_unlock(&inode->i_mutex_); |
| // ret = -EPERM; |
| // goto out; |
| // } |
| // } |
| |
| // flags = flags & FS_FL_USER_MODIFIABLE; |
| // flags |= oldflags & ~FS_FL_USER_MODIFIABLE; |
| // fi->i_flags = flags; |
| // mutex_unlock(&inode->i_mutex_); |
| |
| // f2fs_set_inode_flags(inode); |
| // inode->i_ctime = CURRENT_TIME; |
| // MarkInodeDirty(inode); |
| // out: |
| // mnt_drop_write(filp->f_path.mnt); |
| // return ret; |
| // } |
| // default: |
| // return -ENOTTY; |
| // } |
| // } |
| #endif |
| |
| zx_status_t File::Read(void *data, size_t len, size_t off, size_t *out_actual) { |
| uint64_t blk_start = off / kBlockSize; |
| uint64_t blk_end = (off + len) / kBlockSize; |
| size_t off_in_block = off % kBlockSize; |
| size_t off_in_buf = 0; |
| Page *data_page = nullptr; |
| void *data_buf = nullptr; |
| uint64_t n; |
| size_t left = len; |
| uint64_t npages = (i_size_ + kBlockSize - 1) / kBlockSize; |
| |
| fbl::AutoLock lock(&i_mutex_); |
| |
| if (off >= i_size_) { |
| *out_actual = 0; |
| return ZX_OK; |
| } |
| |
| for (n = blk_start; n <= blk_end; n++) { |
| if (zx_status_t ret = GetLockDataPage(n, &data_page); ret != ZX_OK) { |
| *out_actual = off_in_buf; |
| return ret; |
| } |
| |
| size_t cur_len = std::min(static_cast<size_t>(kBlockSize - off_in_block), left); |
| if (n == npages - 1) { |
| if (i_size_ % kBlockSize > 0) |
| cur_len = std::min(cur_len, static_cast<size_t>(i_size_ % kBlockSize)); |
| } |
| |
| data_buf = PageAddress(data_page); |
| memcpy(static_cast<char *>(data) + off_in_buf, static_cast<char *>(data_buf) + off_in_block, |
| cur_len); |
| |
| off_in_buf += cur_len; |
| left -= cur_len; |
| off_in_block = 0; |
| |
| F2fsPutPage(data_page, 1); |
| data_page = nullptr; |
| |
| if (left == 0) |
| break; |
| |
| if (n == npages - 1) |
| break; |
| } |
| |
| *out_actual = off_in_buf; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t File::Write(const void *data, size_t len, size_t offset, size_t *out_actual) { |
| uint64_t blk_start = offset / kBlockSize; |
| uint64_t blk_end = (offset + len) / kBlockSize; |
| size_t off_in_block = offset % kBlockSize; |
| size_t off_in_buf = 0; |
| Page *data_page = nullptr; |
| void *data_buf = nullptr; |
| uint64_t n; |
| size_t left = len; |
| |
| fbl::AutoLock lock(&i_mutex_); |
| |
| for (n = blk_start; n <= blk_end; n++) { |
| size_t cur_len = std::min(static_cast<size_t>(kBlockSize - off_in_block), left); |
| if (zx_status_t ret = WriteBegin(n * kBlockSize + off_in_block, cur_len, &data_page); |
| ret != ZX_OK) { |
| if (off_in_buf > 0) { |
| 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); |
| } |
| *out_actual = off_in_buf; |
| |
| return ret; |
| } |
| |
| data_buf = PageAddress(data_page); |
| memcpy(static_cast<char *>(data_buf) + off_in_block, |
| static_cast<const char *>(data) + off_in_buf, cur_len); |
| |
| off_in_buf += cur_len; |
| left -= cur_len; |
| off_in_block = 0; |
| |
| i_size_ = std::max(static_cast<size_t>(i_size_), offset + off_in_buf); |
| #if 0 // porting needed |
| // set_page_dirty(data_page, Vfs()); |
| #else |
| FlushDirtyDataPage(Vfs(), data_page); |
| #endif |
| F2fsPutPage(data_page, 1); |
| data_page = nullptr; |
| |
| if (left == 0) |
| break; |
| } |
| |
| if (off_in_buf > 0) { |
| 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); |
| } |
| *out_actual = off_in_buf; |
| |
| return ZX_OK; |
| } |
| |
| } // namespace f2fs |