Re: [PATCH 08/16 v2] f2fs: add file operations

From: Marco Stornelli
Date: Tue Oct 23 2012 - 02:58:50 EST


2012/10/23 Jaegeuk Kim <jaegeuk.kim@xxxxxxxxxxx>:
> This adds memory operations and file/file_inode operations.
>
> - F2FS supports fallocate(), mmap(), fsync(), and basic ioctl().
>
> Signed-off-by: Jaegeuk Kim <jaegeuk.kim@xxxxxxxxxxx>
> ---
> fs/f2fs/file.c | 640 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 640 insertions(+)
> create mode 100644 fs/f2fs/file.c
>
> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
> new file mode 100644
> index 0000000..81b1fd0
> --- /dev/null
> +++ b/fs/f2fs/file.c
> @@ -0,0 +1,640 @@
> +/**
> + * fs/f2fs/file.c
> + *
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com/
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/fs.h>
> +#include <linux/f2fs_fs.h>
> +#include <linux/stat.h>
> +#include <linux/buffer_head.h>
> +#include <linux/writeback.h>
> +#include <linux/falloc.h>
> +#include <linux/types.h>
> +#include <linux/uaccess.h>
> +#include <linux/mount.h>
> +
> +#include "f2fs.h"
> +#include "node.h"
> +#include "segment.h"
> +#include "xattr.h"
> +#include "acl.h"
> +
> +static int f2fs_vm_page_mkwrite(struct vm_area_struct *vma,
> + struct vm_fault *vmf)
> +{
> + struct page *page = vmf->page;
> + struct inode *inode = vma->vm_file->f_path.dentry->d_inode;
> + struct f2fs_sb_info *sbi = F2FS_SB(inode->i_sb);
> + struct page *node_page;
> + block_t old_blk_addr;
> + struct dnode_of_data dn;
> + int err;
> +
> + f2fs_balance_fs(sbi);
> +
> + sb_start_pagefault(inode->i_sb);
> +
> + mutex_lock_op(sbi, DATA_NEW);
> +
> + /* block allocation */
> + set_new_dnode(&dn, inode, NULL, NULL, 0);
> + err = get_dnode_of_data(&dn, page->index, 0);
> + if (err) {
> + mutex_unlock_op(sbi, DATA_NEW);
> + goto out;
> + }
> +
> + old_blk_addr = dn.data_blkaddr;
> + node_page = dn.node_page;
> +
> + if (old_blk_addr == NULL_ADDR) {
> + err = reserve_new_block(&dn);
> + if (err) {
> + f2fs_put_dnode(&dn);
> + mutex_unlock_op(sbi, DATA_NEW);
> + goto out;
> + }
> + }
> + f2fs_put_dnode(&dn);
> +
> + mutex_unlock_op(sbi, DATA_NEW);
> +
> + 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 */
> + wait_on_page_writeback(page);
> +
> + /* page is wholly or partially inside EOF */
> + if (((page->index + 1) << PAGE_CACHE_SHIFT) > i_size_read(inode)) {
> + unsigned offset;
> + offset = i_size_read(inode) & ~PAGE_CACHE_MASK;
> + zero_user_segment(page, offset, PAGE_CACHE_SIZE);
> + }
> + set_page_dirty(page);
> + SetPageUptodate(page);
> +
> + file_update_time(vma->vm_file);
> +out:
> + sb_end_pagefault(inode->i_sb);
> + return block_page_mkwrite_return(err);
> +}
> +
> +static const struct vm_operations_struct f2fs_file_vm_ops = {
> + .fault = filemap_fault,
> + .page_mkwrite = f2fs_vm_page_mkwrite,
> +};
> +
> +static int need_to_sync_dir(struct f2fs_sb_info *sbi, struct inode *inode)
> +{
> + struct dentry *dentry;
> + nid_t pino;
> +
> + inode = igrab(inode);
> + dentry = d_find_any_alias(inode);
> + if (!dentry) {
> + iput(inode);
> + return 0;
> + }
> + pino = dentry->d_parent->d_inode->i_ino;
> + dput(dentry);
> + iput(inode);
> + return !is_checkpointed_node(sbi, pino);
> +}
> +
> +int f2fs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> +{
> + struct inode *inode = file->f_mapping->host;
> + struct f2fs_sb_info *sbi = F2FS_SB(inode->i_sb);
> + unsigned long long cur_version;
> + int ret = 0;
> + bool need_cp = false;
> + struct writeback_control wbc = {
> + .sync_mode = WB_SYNC_ALL,
> + .nr_to_write = LONG_MAX,
> + .for_reclaim = 0,
> + };
> +
> + ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&inode->i_mutex);
> +
> + if (inode->i_sb->s_flags & MS_RDONLY)
> + goto out;
> + if (datasync && !(inode->i_state & I_DIRTY_DATASYNC))
> + goto out;
> +
> + mutex_lock(&sbi->cp_mutex);
> + cur_version = le64_to_cpu(F2FS_CKPT(sbi)->checkpoint_ver);
> + mutex_unlock(&sbi->cp_mutex);
> +
> + if (F2FS_I(inode)->data_version != cur_version &&
> + !(inode->i_state & I_DIRTY))
> + goto out;
> + F2FS_I(inode)->data_version--;
> +
> + if (!S_ISREG(inode->i_mode) || inode->i_nlink != 1)
> + need_cp = true;
> + if (is_inode_flag_set(F2FS_I(inode), FI_NEED_CP))
> + need_cp = true;
> + if (!space_for_roll_forward(sbi))
> + need_cp = true;
> + if (need_to_sync_dir(sbi, inode))
> + need_cp = true;
> +
> + f2fs_write_inode(inode, NULL);
> +
> + if (need_cp) {
> + /* all the dirty node pages should be flushed for POR */
> + ret = f2fs_sync_fs(inode->i_sb, 1);
> + clear_inode_flag(F2FS_I(inode), FI_NEED_CP);
> + } else {
> + while (sync_node_pages(sbi, inode->i_ino, &wbc) == 0)
> + f2fs_write_inode(inode, NULL);
> + filemap_fdatawait_range(sbi->node_inode->i_mapping,
> + 0, LONG_MAX);
> + }
> +out:
> + mutex_unlock(&inode->i_mutex);
> + return ret;
> +}
> +
> +static int f2fs_file_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> + file_accessed(file);
> + vma->vm_ops = &f2fs_file_vm_ops;
> + return 0;
> +}
> +
> +static int truncate_data_blocks_range(struct dnode_of_data *dn, int count)
> +{
> + int nr_free = 0, ofs = dn->ofs_in_node;
> + struct f2fs_sb_info *sbi = F2FS_SB(dn->inode->i_sb);
> + struct f2fs_node *raw_node;
> + __le32 *addr;
> +
> + raw_node = page_address(dn->node_page);
> + addr = blkaddr_in_node(raw_node) + ofs;
> +
> + for ( ; count > 0; count--, addr++, dn->ofs_in_node++) {
> + block_t blkaddr = le32_to_cpu(*addr);
> + if (blkaddr == NULL_ADDR)
> + continue;
> +
> + update_extent_cache(NULL_ADDR, dn);
> + invalidate_blocks(sbi, blkaddr);
> + dec_valid_block_count(sbi, dn->inode, 1);
> + nr_free++;
> + }
> + if (nr_free) {
> + set_page_dirty(dn->node_page);
> + sync_inode_page(dn);
> + }
> + dn->ofs_in_node = ofs;
> + return nr_free;
> +}
> +
> +void truncate_data_blocks(struct dnode_of_data *dn)
> +{
> + truncate_data_blocks_range(dn, ADDRS_PER_BLOCK);
> +}
> +
> +static void truncate_partial_data_page(struct inode *inode, u64 from)
> +{
> + unsigned offset = from & (PAGE_CACHE_SIZE - 1);
> + struct page *page;
> +
> + if (!offset)
> + return;
> +
> + page = find_data_page(inode, from >> PAGE_CACHE_SHIFT);
> + if (IS_ERR(page))
> + return;
> +
> + lock_page(page);
> + wait_on_page_writeback(page);
> + zero_user(page, offset, PAGE_CACHE_SIZE - offset);
> + set_page_dirty(page);
> + f2fs_put_page(page, 1);
> +}
> +
> +static int truncate_blocks(struct inode *inode, u64 from)
> +{
> + struct f2fs_sb_info *sbi = F2FS_SB(inode->i_sb);
> + unsigned int blocksize = inode->i_sb->s_blocksize;
> + struct 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);
> +
> + set_new_dnode(&dn, inode, NULL, NULL, 0);
> + err = get_dnode_of_data(&dn, free_from, RDONLY_NODE);
> + if (err) {
> + if (err == -ENOENT)
> + 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)) {
> + truncate_data_blocks_range(&dn, count);
> + free_from += count;
> + }
> +
> + f2fs_put_dnode(&dn);
> +free_next:
> + err = truncate_inode_blocks(inode, free_from);
> + mutex_unlock_op(sbi, DATA_TRUNC);
> +
> + /* lastly zero out the first data page */
> + truncate_partial_data_page(inode, from);
> +
> + return err;
> +}
> +
> +void f2fs_truncate(struct inode *inode)
> +{
> + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
> + S_ISLNK(inode->i_mode)))
> + return;
> +
> + if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
> + return;

No truncate for an append only file? You call f2fs_truncate from
evict_inode, so no block freeing when this kind of inode is deleted.

> +
> + if (!truncate_blocks(inode, i_size_read(inode))) {
> + inode->i_mtime = inode->i_ctime = CURRENT_TIME;
> + mark_inode_dirty(inode);
> + }
> +
> + f2fs_balance_fs(F2FS_SB(inode->i_sb));
> +}
> +
> +static int f2fs_getattr(struct vfsmount *mnt,
> + struct dentry *dentry, struct kstat *stat)
> +{
> + struct inode *inode = dentry->d_inode;
> + generic_fillattr(inode, stat);
> + stat->blocks <<= 3;
> + return 0;
> +}
> +
> +#ifdef CONFIG_F2FS_FS_POSIX_ACL
> +static void __setattr_copy(struct inode *inode, const struct iattr *attr)
> +{
> + struct f2fs_inode_info *fi = F2FS_I(inode);
> + unsigned int ia_valid = attr->ia_valid;
> +
> + if (ia_valid & ATTR_UID)
> + inode->i_uid = attr->ia_uid;
> + if (ia_valid & ATTR_GID)
> + inode->i_gid = attr->ia_gid;
> + if (ia_valid & ATTR_ATIME)
> + inode->i_atime = timespec_trunc(attr->ia_atime,
> + inode->i_sb->s_time_gran);
> + if (ia_valid & ATTR_MTIME)
> + inode->i_mtime = timespec_trunc(attr->ia_mtime,
> + inode->i_sb->s_time_gran);
> + if (ia_valid & ATTR_CTIME)
> + inode->i_ctime = timespec_trunc(attr->ia_ctime,
> + inode->i_sb->s_time_gran);
> + if (ia_valid & ATTR_MODE) {
> + umode_t mode = attr->ia_mode;
> +
> + if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID))
> + mode &= ~S_ISGID;
> + set_acl_inode(fi, mode);
> + }
> +}
> +#else
> +#define __setattr_copy setattr_copy
> +#endif
> +
> +int f2fs_setattr(struct dentry *dentry, struct iattr *attr)
> +{
> + struct inode *inode = dentry->d_inode;
> + struct f2fs_inode_info *fi = F2FS_I(inode);
> + int err;
> +
> + err = inode_change_ok(inode, attr);
> + if (err)
> + return err;
> +
> + if ((attr->ia_valid & ATTR_SIZE) &&
> + attr->ia_size != i_size_read(inode)) {
> + truncate_setsize(inode, attr->ia_size);
> + f2fs_truncate(inode);

No need to call truncate_pagecache & co.?

Marco
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/