f2fs: support FALLOC_FL_INSERT_RANGE
authorChao Yu <chao2.yu@samsung.com>
Thu, 28 May 2015 11:16:57 +0000 (19:16 +0800)
committerJaegeuk Kim <jaegeuk@kernel.org>
Tue, 2 Jun 2015 16:53:27 +0000 (09:53 -0700)
FALLOC_FL_INSERT_RANGE flag for ->fallocate was introduced in commit
dd46c787788d ("fs: Add support FALLOC_FL_INSERT_RANGE for fallocate").

The effect of FALLOC_FL_INSERT_RANGE command is the opposite of
FALLOC_FL_COLLAPSE_RANGE, if this command was performed, all data from
offset to EOF in our file will be shifted to right as given length, and
then range [offset, offset + length] becomes a hole.

This command is useful for our user who wants to add some data in the
middle of the file, for example: video/music editor will insert a keyframe
in specified position of media file, with this command we can easily create
a hole for inserting without removing original data.

This patch introduces f2fs_insert_range() to support FALLOC_FL_INSERT_RANGE.

Signed-off-by: Chao Yu <chao2.yu@samsung.com>
Signed-off-by: Yuan Zhong <yuan.mark.zhong@samsung.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/file.c

index aec96d3f8222c3b27ffc4698166aea5800b43a1f..4d42d66acd96cfff6f7b3b2bbdad52f94c2539d0 100644 (file)
@@ -1010,6 +1010,100 @@ out:
        return ret;
 }
 
+static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len)
+{
+       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+       pgoff_t pg_start, pg_end, delta, nrpages, idx;
+       loff_t new_size;
+       int ret;
+
+       if (!S_ISREG(inode->i_mode))
+               return -EINVAL;
+
+       new_size = i_size_read(inode) + len;
+       if (new_size > inode->i_sb->s_maxbytes)
+               return -EFBIG;
+
+       if (offset >= i_size_read(inode))
+               return -EINVAL;
+
+       /* insert range should be aligned to block size of f2fs. */
+       if (offset & (F2FS_BLKSIZE - 1) || len & (F2FS_BLKSIZE - 1))
+               return -EINVAL;
+
+       f2fs_balance_fs(sbi);
+
+       ret = truncate_blocks(inode, i_size_read(inode), true);
+       if (ret)
+               return ret;
+
+       /* write out all dirty pages from offset */
+       ret = filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
+       if (ret)
+               return ret;
+
+       truncate_pagecache(inode, offset);
+
+       pg_start = offset >> PAGE_CACHE_SHIFT;
+       pg_end = (offset + len) >> PAGE_CACHE_SHIFT;
+       delta = pg_end - pg_start;
+       nrpages = (i_size_read(inode) + PAGE_SIZE - 1) / PAGE_SIZE;
+
+       for (idx = nrpages - 1; idx >= pg_start && idx != -1; idx--) {
+               struct dnode_of_data dn;
+               struct page *ipage;
+               block_t new_addr, old_addr;
+
+               f2fs_lock_op(sbi);
+
+               set_new_dnode(&dn, inode, NULL, NULL, 0);
+               ret = get_dnode_of_data(&dn, idx, LOOKUP_NODE_RA);
+               if (ret && ret != -ENOENT) {
+                       goto out;
+               } else if (ret == -ENOENT) {
+                       goto next;
+               } else if (dn.data_blkaddr == NULL_ADDR) {
+                       f2fs_put_dnode(&dn);
+                       goto next;
+               } else {
+                       new_addr = dn.data_blkaddr;
+                       truncate_data_blocks_range(&dn, 1);
+                       f2fs_put_dnode(&dn);
+               }
+
+               ipage = get_node_page(sbi, inode->i_ino);
+               if (IS_ERR(ipage)) {
+                       ret = PTR_ERR(ipage);
+                       goto out;
+               }
+
+               set_new_dnode(&dn, inode, ipage, NULL, 0);
+               ret = f2fs_reserve_block(&dn, idx + delta);
+               if (ret)
+                       goto out;
+
+               old_addr = dn.data_blkaddr;
+               f2fs_bug_on(sbi, old_addr != NEW_ADDR);
+
+               if (new_addr != NEW_ADDR) {
+                       struct node_info ni;
+
+                       get_node_info(sbi, dn.nid, &ni);
+                       f2fs_replace_block(sbi, &dn, old_addr, new_addr,
+                                                       ni.version, true);
+               }
+               f2fs_put_dnode(&dn);
+next:
+               f2fs_unlock_op(sbi);
+       }
+
+       i_size_write(inode, new_size);
+       return 0;
+out:
+       f2fs_unlock_op(sbi);
+       return ret;
+}
+
 static int expand_inode_data(struct inode *inode, loff_t offset,
                                        loff_t len, int mode)
 {
@@ -1077,11 +1171,13 @@ static long f2fs_fallocate(struct file *file, int mode,
        struct inode *inode = file_inode(file);
        long ret = 0;
 
-       if (f2fs_encrypted_inode(inode) && (mode & FALLOC_FL_COLLAPSE_RANGE))
+       if (f2fs_encrypted_inode(inode) &&
+               (mode & (FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_INSERT_RANGE)))
                return -EOPNOTSUPP;
 
        if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
-                       FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE))
+                       FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE |
+                       FALLOC_FL_INSERT_RANGE))
                return -EOPNOTSUPP;
 
        mutex_lock(&inode->i_mutex);
@@ -1095,6 +1191,8 @@ static long f2fs_fallocate(struct file *file, int mode,
                ret = f2fs_collapse_range(inode, offset, len);
        } else if (mode & FALLOC_FL_ZERO_RANGE) {
                ret = f2fs_zero_range(inode, offset, len, mode);
+       } else if (mode & FALLOC_FL_INSERT_RANGE) {
+               ret = f2fs_insert_range(inode, offset, len);
        } else {
                ret = expand_inode_data(inode, offset, len, mode);
        }