Merge tag 'wireless-drivers-2020-12-22' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-microblaze.git] / fs / f2fs / file.c
index 8a42240..f585545 100644 (file)
@@ -31,6 +31,7 @@
 #include "gc.h"
 #include "trace.h"
 #include <trace/events/f2fs.h>
+#include <uapi/linux/f2fs.h>
 
 static vm_fault_t f2fs_filemap_fault(struct vm_fault *vmf)
 {
@@ -376,32 +377,15 @@ int f2fs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
        return f2fs_do_sync_file(file, start, end, datasync, false);
 }
 
-static pgoff_t __get_first_dirty_index(struct address_space *mapping,
-                                               pgoff_t pgofs, int whence)
-{
-       struct page *page;
-       int nr_pages;
-
-       if (whence != SEEK_DATA)
-               return 0;
-
-       /* find first dirty page index */
-       nr_pages = find_get_pages_tag(mapping, &pgofs, PAGECACHE_TAG_DIRTY,
-                                     1, &page);
-       if (!nr_pages)
-               return ULONG_MAX;
-       pgofs = page->index;
-       put_page(page);
-       return pgofs;
-}
-
-static bool __found_offset(struct f2fs_sb_info *sbi, block_t blkaddr,
-                               pgoff_t dirty, pgoff_t pgofs, int whence)
+static bool __found_offset(struct address_space *mapping, block_t blkaddr,
+                               pgoff_t index, int whence)
 {
        switch (whence) {
        case SEEK_DATA:
-               if ((blkaddr == NEW_ADDR && dirty == pgofs) ||
-                       __is_valid_data_blkaddr(blkaddr))
+               if (__is_valid_data_blkaddr(blkaddr))
+                       return true;
+               if (blkaddr == NEW_ADDR &&
+                   xa_get_mark(&mapping->i_pages, index, PAGECACHE_TAG_DIRTY))
                        return true;
                break;
        case SEEK_HOLE:
@@ -417,7 +401,7 @@ static loff_t f2fs_seek_block(struct file *file, loff_t offset, int whence)
        struct inode *inode = file->f_mapping->host;
        loff_t maxbytes = inode->i_sb->s_maxbytes;
        struct dnode_of_data dn;
-       pgoff_t pgofs, end_offset, dirty;
+       pgoff_t pgofs, end_offset;
        loff_t data_ofs = offset;
        loff_t isize;
        int err = 0;
@@ -429,16 +413,18 @@ static loff_t f2fs_seek_block(struct file *file, loff_t offset, int whence)
                goto fail;
 
        /* handle inline data case */
-       if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode)) {
-               if (whence == SEEK_HOLE)
+       if (f2fs_has_inline_data(inode)) {
+               if (whence == SEEK_HOLE) {
                        data_ofs = isize;
-               goto found;
+                       goto found;
+               } else if (whence == SEEK_DATA) {
+                       data_ofs = offset;
+                       goto found;
+               }
        }
 
        pgofs = (pgoff_t)(offset >> PAGE_SHIFT);
 
-       dirty = __get_first_dirty_index(inode->i_mapping, pgofs, whence);
-
        for (; data_ofs < isize; data_ofs = (loff_t)pgofs << PAGE_SHIFT) {
                set_new_dnode(&dn, inode, NULL, NULL, 0);
                err = f2fs_get_dnode_of_data(&dn, pgofs, LOOKUP_NODE);
@@ -471,7 +457,7 @@ static loff_t f2fs_seek_block(struct file *file, loff_t offset, int whence)
                                goto fail;
                        }
 
-                       if (__found_offset(F2FS_I_SB(inode), blkaddr, dirty,
+                       if (__found_offset(file->f_mapping, blkaddr,
                                                        pgofs, whence)) {
                                f2fs_put_dnode(&dn);
                                goto found;
@@ -564,7 +550,7 @@ void f2fs_truncate_data_blocks_range(struct dnode_of_data *dn, int count)
        bool compressed_cluster = false;
        int cluster_index = 0, valid_blocks = 0;
        int cluster_size = F2FS_I(dn->inode)->i_cluster_size;
-       bool released = !F2FS_I(dn->inode)->i_compr_blocks;
+       bool released = !atomic_read(&F2FS_I(dn->inode)->i_compr_blocks);
 
        if (IS_INODE(dn->node_page) && f2fs_has_extra_attr(dn->inode))
                base = get_extra_isize(dn->inode);
@@ -753,11 +739,14 @@ int f2fs_truncate_blocks(struct inode *inode, u64 from, bool lock)
                return err;
 
 #ifdef CONFIG_F2FS_FS_COMPRESSION
-       if (from != free_from)
+       if (from != free_from) {
                err = f2fs_truncate_partial_cluster(inode, from, lock);
+               if (err)
+                       return err;
+       }
 #endif
 
-       return err;
+       return 0;
 }
 
 int f2fs_truncate(struct inode *inode)
@@ -1656,13 +1645,14 @@ next_alloc:
                }
 
                down_write(&sbi->pin_sem);
-               map.m_seg_type = CURSEG_COLD_DATA_PINNED;
 
                f2fs_lock_op(sbi);
-               f2fs_allocate_new_segment(sbi, CURSEG_COLD_DATA);
+               f2fs_allocate_new_segment(sbi, CURSEG_COLD_DATA_PINNED);
                f2fs_unlock_op(sbi);
 
+               map.m_seg_type = CURSEG_COLD_DATA_PINNED;
                err = f2fs_map_blocks(inode, &map, 1, F2FS_GET_BLOCK_PRE_DIO);
+
                up_write(&sbi->pin_sem);
 
                done += map.m_len;
@@ -1828,7 +1818,7 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask)
 
        if ((iflags ^ masked_flags) & F2FS_COMPR_FL) {
                if (masked_flags & F2FS_COMPR_FL) {
-                       if (f2fs_disable_compressed_file(inode))
+                       if (!f2fs_disable_compressed_file(inode))
                                return -EINVAL;
                }
                if (iflags & F2FS_NOCOMP_FL)
@@ -1836,6 +1826,8 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask)
                if (iflags & F2FS_COMPR_FL) {
                        if (!f2fs_may_compress(inode))
                                return -EINVAL;
+                       if (S_ISREG(inode->i_mode) && inode->i_size)
+                               return -EINVAL;
 
                        set_compress_context(inode);
                }
@@ -2244,16 +2236,12 @@ static int f2fs_ioc_shutdown(struct file *filp, unsigned long arg)
 
        switch (in) {
        case F2FS_GOING_DOWN_FULLSYNC:
-               sb = freeze_bdev(sb->s_bdev);
-               if (IS_ERR(sb)) {
-                       ret = PTR_ERR(sb);
+               ret = freeze_bdev(sb->s_bdev);
+               if (ret)
                        goto out;
-               }
-               if (sb) {
-                       f2fs_stop_checkpoint(sbi, false);
-                       set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
-                       thaw_bdev(sb->s_bdev, sb);
-               }
+               f2fs_stop_checkpoint(sbi, false);
+               set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
+               thaw_bdev(sb->s_bdev);
                break;
        case F2FS_GOING_DOWN_METASYNC:
                /* do checkpoint only */
@@ -2488,26 +2476,19 @@ out:
        return ret;
 }
 
-static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
+static int __f2fs_ioc_gc_range(struct file *filp, struct f2fs_gc_range *range)
 {
-       struct inode *inode = file_inode(filp);
-       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
-       struct f2fs_gc_range range;
+       struct f2fs_sb_info *sbi = F2FS_I_SB(file_inode(filp));
        u64 end;
        int ret;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
-
-       if (copy_from_user(&range, (struct f2fs_gc_range __user *)arg,
-                                                       sizeof(range)))
-               return -EFAULT;
-
        if (f2fs_readonly(sbi->sb))
                return -EROFS;
 
-       end = range.start + range.len;
-       if (end < range.start || range.start < MAIN_BLKADDR(sbi) ||
+       end = range->start + range->len;
+       if (end < range->start || range->start < MAIN_BLKADDR(sbi) ||
                                        end >= MAX_BLKADDR(sbi))
                return -EINVAL;
 
@@ -2516,7 +2497,7 @@ static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
                return ret;
 
 do_more:
-       if (!range.sync) {
+       if (!range->sync) {
                if (!down_write_trylock(&sbi->gc_lock)) {
                        ret = -EBUSY;
                        goto out;
@@ -2525,20 +2506,30 @@ do_more:
                down_write(&sbi->gc_lock);
        }
 
-       ret = f2fs_gc(sbi, range.sync, true, GET_SEGNO(sbi, range.start));
+       ret = f2fs_gc(sbi, range->sync, true, GET_SEGNO(sbi, range->start));
        if (ret) {
                if (ret == -EBUSY)
                        ret = -EAGAIN;
                goto out;
        }
-       range.start += BLKS_PER_SEC(sbi);
-       if (range.start <= end)
+       range->start += BLKS_PER_SEC(sbi);
+       if (range->start <= end)
                goto do_more;
 out:
        mnt_drop_write_file(filp);
        return ret;
 }
 
+static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
+{
+       struct f2fs_gc_range range;
+
+       if (copy_from_user(&range, (struct f2fs_gc_range __user *)arg,
+                                                       sizeof(range)))
+               return -EFAULT;
+       return __f2fs_ioc_gc_range(filp, &range);
+}
+
 static int f2fs_ioc_write_checkpoint(struct file *filp, unsigned long arg)
 {
        struct inode *inode = file_inode(filp);
@@ -2783,6 +2774,9 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in,
        if (IS_ENCRYPTED(src) || IS_ENCRYPTED(dst))
                return -EOPNOTSUPP;
 
+       if (pos_out < 0 || pos_in < 0)
+               return -EINVAL;
+
        if (src == dst) {
                if (pos_in == pos_out)
                        return 0;
@@ -2872,9 +2866,9 @@ out:
        return ret;
 }
 
-static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
+static int __f2fs_ioc_move_range(struct file *filp,
+                               struct f2fs_move_range *range)
 {
-       struct f2fs_move_range range;
        struct fd dst;
        int err;
 
@@ -2882,11 +2876,7 @@ static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
                        !(filp->f_mode & FMODE_WRITE))
                return -EBADF;
 
-       if (copy_from_user(&range, (struct f2fs_move_range __user *)arg,
-                                                       sizeof(range)))
-               return -EFAULT;
-
-       dst = fdget(range.dst_fd);
+       dst = fdget(range->dst_fd);
        if (!dst.file)
                return -EBADF;
 
@@ -2899,21 +2889,25 @@ static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
        if (err)
                goto err_out;
 
-       err = f2fs_move_file_range(filp, range.pos_in, dst.file,
-                                       range.pos_out, range.len);
+       err = f2fs_move_file_range(filp, range->pos_in, dst.file,
+                                       range->pos_out, range->len);
 
        mnt_drop_write_file(filp);
-       if (err)
-               goto err_out;
-
-       if (copy_to_user((struct f2fs_move_range __user *)arg,
-                                               &range, sizeof(range)))
-               err = -EFAULT;
 err_out:
        fdput(dst);
        return err;
 }
 
+static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
+{
+       struct f2fs_move_range range;
+
+       if (copy_from_user(&range, (struct f2fs_move_range __user *)arg,
+                                                       sizeof(range)))
+               return -EFAULT;
+       return __f2fs_ioc_move_range(filp, &range);
+}
+
 static int f2fs_ioc_flush_device(struct file *filp, unsigned long arg)
 {
        struct inode *inode = file_inode(filp);
@@ -3258,7 +3252,7 @@ static int f2fs_ioc_set_pin_file(struct file *filp, unsigned long arg)
        if (ret)
                goto out;
 
-       if (f2fs_disable_compressed_file(inode)) {
+       if (!f2fs_disable_compressed_file(inode)) {
                ret = -EOPNOTSUPP;
                goto out;
        }
@@ -3385,7 +3379,7 @@ static int f2fs_ioc_getfslabel(struct file *filp, unsigned long arg)
                                min(FSLABEL_MAX, count)))
                err = -EFAULT;
 
-       kvfree(vbuf);
+       kfree(vbuf);
        return err;
 }
 
@@ -3436,7 +3430,7 @@ static int f2fs_get_compress_blocks(struct file *filp, unsigned long arg)
        if (!f2fs_compressed_file(inode))
                return -EINVAL;
 
-       blocks = F2FS_I(inode)->i_compr_blocks;
+       blocks = atomic_read(&F2FS_I(inode)->i_compr_blocks);
        return put_user(blocks, (u64 __user *)arg);
 }
 
@@ -3521,7 +3515,8 @@ static int f2fs_release_compress_blocks(struct file *filp, unsigned long arg)
        inode_lock(inode);
 
        writecount = atomic_read(&inode->i_writecount);
-       if ((filp->f_mode & FMODE_WRITE && writecount != 1) || writecount) {
+       if ((filp->f_mode & FMODE_WRITE && writecount != 1) ||
+                       (!(filp->f_mode & FMODE_WRITE) && writecount)) {
                ret = -EBUSY;
                goto out;
        }
@@ -3540,7 +3535,7 @@ static int f2fs_release_compress_blocks(struct file *filp, unsigned long arg)
        inode->i_ctime = current_time(inode);
        f2fs_mark_inode_dirty_sync(inode, true);
 
-       if (!F2FS_I(inode)->i_compr_blocks)
+       if (!atomic_read(&F2FS_I(inode)->i_compr_blocks))
                goto out;
 
        down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
@@ -3588,14 +3583,15 @@ out:
 
        if (ret >= 0) {
                ret = put_user(released_blocks, (u64 __user *)arg);
-       } else if (released_blocks && F2FS_I(inode)->i_compr_blocks) {
+       } else if (released_blocks &&
+                       atomic_read(&F2FS_I(inode)->i_compr_blocks)) {
                set_sbi_flag(sbi, SBI_NEED_FSCK);
                f2fs_warn(sbi, "%s: partial blocks were released i_ino=%lx "
-                       "iblocks=%llu, released=%u, compr_blocks=%llu, "
+                       "iblocks=%llu, released=%u, compr_blocks=%u, "
                        "run fsck to fix.",
                        __func__, inode->i_ino, inode->i_blocks,
                        released_blocks,
-                       F2FS_I(inode)->i_compr_blocks);
+                       atomic_read(&F2FS_I(inode)->i_compr_blocks));
        }
 
        return ret;
@@ -3683,7 +3679,7 @@ static int f2fs_reserve_compress_blocks(struct file *filp, unsigned long arg)
        if (ret)
                return ret;
 
-       if (F2FS_I(inode)->i_compr_blocks)
+       if (atomic_read(&F2FS_I(inode)->i_compr_blocks))
                goto out;
 
        f2fs_balance_fs(F2FS_I_SB(inode), true);
@@ -3747,14 +3743,15 @@ out:
 
        if (ret >= 0) {
                ret = put_user(reserved_blocks, (u64 __user *)arg);
-       } else if (reserved_blocks && F2FS_I(inode)->i_compr_blocks) {
+       } else if (reserved_blocks &&
+                       atomic_read(&F2FS_I(inode)->i_compr_blocks)) {
                set_sbi_flag(sbi, SBI_NEED_FSCK);
                f2fs_warn(sbi, "%s: partial blocks were released i_ino=%lx "
-                       "iblocks=%llu, reserved=%u, compr_blocks=%llu, "
+                       "iblocks=%llu, reserved=%u, compr_blocks=%u, "
                        "run fsck to fix.",
                        __func__, inode->i_ino, inode->i_blocks,
                        reserved_blocks,
-                       F2FS_I(inode)->i_compr_blocks);
+                       atomic_read(&F2FS_I(inode)->i_compr_blocks));
        }
 
        return ret;
@@ -3947,13 +3944,265 @@ err:
        return ret;
 }
 
-long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+static int f2fs_ioc_get_compress_option(struct file *filp, unsigned long arg)
 {
-       if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
-               return -EIO;
-       if (!f2fs_is_checkpoint_ready(F2FS_I_SB(file_inode(filp))))
-               return -ENOSPC;
+       struct inode *inode = file_inode(filp);
+       struct f2fs_comp_option option;
+
+       if (!f2fs_sb_has_compression(F2FS_I_SB(inode)))
+               return -EOPNOTSUPP;
+
+       inode_lock_shared(inode);
+
+       if (!f2fs_compressed_file(inode)) {
+               inode_unlock_shared(inode);
+               return -ENODATA;
+       }
+
+       option.algorithm = F2FS_I(inode)->i_compress_algorithm;
+       option.log_cluster_size = F2FS_I(inode)->i_log_cluster_size;
+
+       inode_unlock_shared(inode);
+
+       if (copy_to_user((struct f2fs_comp_option __user *)arg, &option,
+                               sizeof(option)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int f2fs_ioc_set_compress_option(struct file *filp, unsigned long arg)
+{
+       struct inode *inode = file_inode(filp);
+       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+       struct f2fs_comp_option option;
+       int ret = 0;
+
+       if (!f2fs_sb_has_compression(sbi))
+               return -EOPNOTSUPP;
+
+       if (!(filp->f_mode & FMODE_WRITE))
+               return -EBADF;
+
+       if (copy_from_user(&option, (struct f2fs_comp_option __user *)arg,
+                               sizeof(option)))
+               return -EFAULT;
+
+       if (!f2fs_compressed_file(inode) ||
+                       option.log_cluster_size < MIN_COMPRESS_LOG_SIZE ||
+                       option.log_cluster_size > MAX_COMPRESS_LOG_SIZE ||
+                       option.algorithm >= COMPRESS_MAX)
+               return -EINVAL;
+
+       file_start_write(filp);
+       inode_lock(inode);
+
+       if (f2fs_is_mmap_file(inode) || get_dirty_pages(inode)) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       if (inode->i_size != 0) {
+               ret = -EFBIG;
+               goto out;
+       }
+
+       F2FS_I(inode)->i_compress_algorithm = option.algorithm;
+       F2FS_I(inode)->i_log_cluster_size = option.log_cluster_size;
+       F2FS_I(inode)->i_cluster_size = 1 << option.log_cluster_size;
+       f2fs_mark_inode_dirty_sync(inode, true);
+
+       if (!f2fs_is_compress_backend_ready(inode))
+               f2fs_warn(sbi, "compression algorithm is successfully set, "
+                       "but current kernel doesn't support this algorithm.");
+out:
+       inode_unlock(inode);
+       file_end_write(filp);
+
+       return ret;
+}
+
+static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
+{
+       DEFINE_READAHEAD(ractl, NULL, inode->i_mapping, page_idx);
+       struct address_space *mapping = inode->i_mapping;
+       struct page *page;
+       pgoff_t redirty_idx = page_idx;
+       int i, page_len = 0, ret = 0;
+
+       page_cache_ra_unbounded(&ractl, len, 0);
+
+       for (i = 0; i < len; i++, page_idx++) {
+               page = read_cache_page(mapping, page_idx, NULL, NULL);
+               if (IS_ERR(page)) {
+                       ret = PTR_ERR(page);
+                       break;
+               }
+               page_len++;
+       }
+
+       for (i = 0; i < page_len; i++, redirty_idx++) {
+               page = find_lock_page(mapping, redirty_idx);
+               if (!page)
+                       ret = -ENOENT;
+               set_page_dirty(page);
+               f2fs_put_page(page, 1);
+               f2fs_put_page(page, 0);
+       }
+
+       return ret;
+}
+
+static int f2fs_ioc_decompress_file(struct file *filp, unsigned long arg)
+{
+       struct inode *inode = file_inode(filp);
+       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+       struct f2fs_inode_info *fi = F2FS_I(inode);
+       pgoff_t page_idx = 0, last_idx;
+       unsigned int blk_per_seg = sbi->blocks_per_seg;
+       int cluster_size = F2FS_I(inode)->i_cluster_size;
+       int count, ret;
+
+       if (!f2fs_sb_has_compression(sbi) ||
+                       F2FS_OPTION(sbi).compress_mode != COMPR_MODE_USER)
+               return -EOPNOTSUPP;
+
+       if (!(filp->f_mode & FMODE_WRITE))
+               return -EBADF;
+
+       if (!f2fs_compressed_file(inode))
+               return -EINVAL;
+
+       f2fs_balance_fs(F2FS_I_SB(inode), true);
+
+       file_start_write(filp);
+       inode_lock(inode);
+
+       if (!f2fs_is_compress_backend_ready(inode)) {
+               ret = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (f2fs_is_mmap_file(inode)) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       ret = filemap_write_and_wait_range(inode->i_mapping, 0, LLONG_MAX);
+       if (ret)
+               goto out;
+
+       if (!atomic_read(&fi->i_compr_blocks))
+               goto out;
+
+       last_idx = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
+
+       count = last_idx - page_idx;
+       while (count) {
+               int len = min(cluster_size, count);
+
+               ret = redirty_blocks(inode, page_idx, len);
+               if (ret < 0)
+                       break;
 
+               if (get_dirty_pages(inode) >= blk_per_seg)
+                       filemap_fdatawrite(inode->i_mapping);
+
+               count -= len;
+               page_idx += len;
+       }
+
+       if (!ret)
+               ret = filemap_write_and_wait_range(inode->i_mapping, 0,
+                                                       LLONG_MAX);
+
+       if (ret)
+               f2fs_warn(sbi, "%s: The file might be partially decompressed "
+                               "(errno=%d). Please delete the file.\n",
+                               __func__, ret);
+out:
+       inode_unlock(inode);
+       file_end_write(filp);
+
+       return ret;
+}
+
+static int f2fs_ioc_compress_file(struct file *filp, unsigned long arg)
+{
+       struct inode *inode = file_inode(filp);
+       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+       pgoff_t page_idx = 0, last_idx;
+       unsigned int blk_per_seg = sbi->blocks_per_seg;
+       int cluster_size = F2FS_I(inode)->i_cluster_size;
+       int count, ret;
+
+       if (!f2fs_sb_has_compression(sbi) ||
+                       F2FS_OPTION(sbi).compress_mode != COMPR_MODE_USER)
+               return -EOPNOTSUPP;
+
+       if (!(filp->f_mode & FMODE_WRITE))
+               return -EBADF;
+
+       if (!f2fs_compressed_file(inode))
+               return -EINVAL;
+
+       f2fs_balance_fs(F2FS_I_SB(inode), true);
+
+       file_start_write(filp);
+       inode_lock(inode);
+
+       if (!f2fs_is_compress_backend_ready(inode)) {
+               ret = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (f2fs_is_mmap_file(inode)) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       ret = filemap_write_and_wait_range(inode->i_mapping, 0, LLONG_MAX);
+       if (ret)
+               goto out;
+
+       set_inode_flag(inode, FI_ENABLE_COMPRESS);
+
+       last_idx = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
+
+       count = last_idx - page_idx;
+       while (count) {
+               int len = min(cluster_size, count);
+
+               ret = redirty_blocks(inode, page_idx, len);
+               if (ret < 0)
+                       break;
+
+               if (get_dirty_pages(inode) >= blk_per_seg)
+                       filemap_fdatawrite(inode->i_mapping);
+
+               count -= len;
+               page_idx += len;
+       }
+
+       if (!ret)
+               ret = filemap_write_and_wait_range(inode->i_mapping, 0,
+                                                       LLONG_MAX);
+
+       clear_inode_flag(inode, FI_ENABLE_COMPRESS);
+
+       if (ret)
+               f2fs_warn(sbi, "%s: The file might be partially compressed "
+                               "(errno=%d). Please delete the file.\n",
+                               __func__, ret);
+out:
+       inode_unlock(inode);
+       file_end_write(filp);
+
+       return ret;
+}
+
+static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
        switch (cmd) {
        case FS_IOC_GETFLAGS:
                return f2fs_ioc_getflags(filp, arg);
@@ -4035,11 +4284,29 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return f2fs_reserve_compress_blocks(filp, arg);
        case F2FS_IOC_SEC_TRIM_FILE:
                return f2fs_sec_trim_file(filp, arg);
+       case F2FS_IOC_GET_COMPRESS_OPTION:
+               return f2fs_ioc_get_compress_option(filp, arg);
+       case F2FS_IOC_SET_COMPRESS_OPTION:
+               return f2fs_ioc_set_compress_option(filp, arg);
+       case F2FS_IOC_DECOMPRESS_FILE:
+               return f2fs_ioc_decompress_file(filp, arg);
+       case F2FS_IOC_COMPRESS_FILE:
+               return f2fs_ioc_compress_file(filp, arg);
        default:
                return -ENOTTY;
        }
 }
 
+long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+       if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
+               return -EIO;
+       if (!f2fs_is_checkpoint_ready(F2FS_I_SB(file_inode(filp))))
+               return -ENOSPC;
+
+       return __f2fs_ioctl(filp, cmd, arg);
+}
+
 static ssize_t f2fs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
 {
        struct file *file = iocb->ki_filp;
@@ -4156,8 +4423,63 @@ out:
 }
 
 #ifdef CONFIG_COMPAT
+struct compat_f2fs_gc_range {
+       u32 sync;
+       compat_u64 start;
+       compat_u64 len;
+};
+#define F2FS_IOC32_GARBAGE_COLLECT_RANGE       _IOW(F2FS_IOCTL_MAGIC, 11,\
+                                               struct compat_f2fs_gc_range)
+
+static int f2fs_compat_ioc_gc_range(struct file *file, unsigned long arg)
+{
+       struct compat_f2fs_gc_range __user *urange;
+       struct f2fs_gc_range range;
+       int err;
+
+       urange = compat_ptr(arg);
+       err = get_user(range.sync, &urange->sync);
+       err |= get_user(range.start, &urange->start);
+       err |= get_user(range.len, &urange->len);
+       if (err)
+               return -EFAULT;
+
+       return __f2fs_ioc_gc_range(file, &range);
+}
+
+struct compat_f2fs_move_range {
+       u32 dst_fd;
+       compat_u64 pos_in;
+       compat_u64 pos_out;
+       compat_u64 len;
+};
+#define F2FS_IOC32_MOVE_RANGE          _IOWR(F2FS_IOCTL_MAGIC, 9,      \
+                                       struct compat_f2fs_move_range)
+
+static int f2fs_compat_ioc_move_range(struct file *file, unsigned long arg)
+{
+       struct compat_f2fs_move_range __user *urange;
+       struct f2fs_move_range range;
+       int err;
+
+       urange = compat_ptr(arg);
+       err = get_user(range.dst_fd, &urange->dst_fd);
+       err |= get_user(range.pos_in, &urange->pos_in);
+       err |= get_user(range.pos_out, &urange->pos_out);
+       err |= get_user(range.len, &urange->len);
+       if (err)
+               return -EFAULT;
+
+       return __f2fs_ioc_move_range(file, &range);
+}
+
 long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
+       if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(file)))))
+               return -EIO;
+       if (!f2fs_is_checkpoint_ready(F2FS_I_SB(file_inode(file))))
+               return -ENOSPC;
+
        switch (cmd) {
        case FS_IOC32_GETFLAGS:
                cmd = FS_IOC_GETFLAGS;
@@ -4168,6 +4490,10 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        case FS_IOC32_GETVERSION:
                cmd = FS_IOC_GETVERSION;
                break;
+       case F2FS_IOC32_GARBAGE_COLLECT_RANGE:
+               return f2fs_compat_ioc_gc_range(file, arg);
+       case F2FS_IOC32_MOVE_RANGE:
+               return f2fs_compat_ioc_move_range(file, arg);
        case F2FS_IOC_START_ATOMIC_WRITE:
        case F2FS_IOC_COMMIT_ATOMIC_WRITE:
        case F2FS_IOC_START_VOLATILE_WRITE:
@@ -4185,10 +4511,8 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
        case FS_IOC_GET_ENCRYPTION_NONCE:
        case F2FS_IOC_GARBAGE_COLLECT:
-       case F2FS_IOC_GARBAGE_COLLECT_RANGE:
        case F2FS_IOC_WRITE_CHECKPOINT:
        case F2FS_IOC_DEFRAGMENT:
-       case F2FS_IOC_MOVE_RANGE:
        case F2FS_IOC_FLUSH_DEVICE:
        case F2FS_IOC_GET_FEATURES:
        case FS_IOC_FSGETXATTR:
@@ -4205,11 +4529,15 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        case F2FS_IOC_RELEASE_COMPRESS_BLOCKS:
        case F2FS_IOC_RESERVE_COMPRESS_BLOCKS:
        case F2FS_IOC_SEC_TRIM_FILE:
+       case F2FS_IOC_GET_COMPRESS_OPTION:
+       case F2FS_IOC_SET_COMPRESS_OPTION:
+       case F2FS_IOC_DECOMPRESS_FILE:
+       case F2FS_IOC_COMPRESS_FILE:
                break;
        default:
                return -ENOIOCTLCMD;
        }
-       return f2fs_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
+       return __f2fs_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
 }
 #endif