f2fs: support file defragment
[deliverable/linux.git] / fs / f2fs / file.c
index a197215ad52bf63f4a571412ffb27b0bde9f648c..2f392982c5970f157a5bee91e3201a474605ee78 100644 (file)
@@ -1646,6 +1646,199 @@ static int f2fs_ioc_write_checkpoint(struct file *filp, unsigned long arg)
        return 0;
 }
 
+static int f2fs_defragment_range(struct f2fs_sb_info *sbi,
+                                       struct file *filp,
+                                       struct f2fs_defragment *range)
+{
+       struct inode *inode = file_inode(filp);
+       struct f2fs_map_blocks map;
+       struct extent_info ei;
+       pgoff_t pg_start, pg_end;
+       unsigned int blk_per_seg = 1 << sbi->log_blocks_per_seg;
+       unsigned int total = 0, sec_num;
+       unsigned int pages_per_sec = sbi->segs_per_sec *
+                                       (1 << sbi->log_blocks_per_seg);
+       block_t blk_end = 0;
+       bool fragmented = false;
+       int err;
+
+       /* if in-place-update policy is enabled, don't waste time here */
+       if (need_inplace_update(inode))
+               return -EINVAL;
+
+       pg_start = range->start >> PAGE_CACHE_SHIFT;
+       pg_end = (range->start + range->len) >> PAGE_CACHE_SHIFT;
+
+       f2fs_balance_fs(sbi);
+
+       mutex_lock(&inode->i_mutex);
+
+       /* writeback all dirty pages in the range */
+       err = filemap_write_and_wait_range(inode->i_mapping, range->start,
+                                               range->start + range->len);
+       if (err)
+               goto out;
+
+       /*
+        * lookup mapping info in extent cache, skip defragmenting if physical
+        * block addresses are continuous.
+        */
+       if (f2fs_lookup_extent_cache(inode, pg_start, &ei)) {
+               if (ei.fofs + ei.len >= pg_end)
+                       goto out;
+       }
+
+       map.m_lblk = pg_start;
+       map.m_len = pg_end - pg_start;
+
+       /*
+        * lookup mapping info in dnode page cache, skip defragmenting if all
+        * physical block addresses are continuous even if there are hole(s)
+        * in logical blocks.
+        */
+       while (map.m_lblk < pg_end) {
+               map.m_flags = 0;
+               err = f2fs_map_blocks(inode, &map, 0, F2FS_GET_BLOCK_READ);
+               if (err)
+                       goto out;
+
+               if (!(map.m_flags & F2FS_MAP_FLAGS)) {
+                       map.m_lblk++;
+                       map.m_len--;
+                       continue;
+               }
+
+               if (blk_end && blk_end != map.m_pblk) {
+                       fragmented = true;
+                       break;
+               }
+               blk_end = map.m_pblk + map.m_len;
+
+               map.m_lblk += map.m_len;
+               map.m_len = pg_end - map.m_lblk;
+       }
+
+       if (!fragmented)
+               goto out;
+
+       map.m_lblk = pg_start;
+       map.m_len = pg_end - pg_start;
+
+       sec_num = (map.m_len + pages_per_sec - 1) / pages_per_sec;
+
+       /*
+        * make sure there are enough free section for LFS allocation, this can
+        * avoid defragment running in SSR mode when free section are allocated
+        * intensively
+        */
+       if (has_not_enough_free_secs(sbi, sec_num)) {
+               err = -EAGAIN;
+               goto out;
+       }
+
+       while (map.m_lblk < pg_end) {
+               pgoff_t idx;
+               int cnt = 0;
+
+do_map:
+               map.m_flags = 0;
+               err = f2fs_map_blocks(inode, &map, 0, F2FS_GET_BLOCK_READ);
+               if (err)
+                       goto clear_out;
+
+               if (!(map.m_flags & F2FS_MAP_FLAGS)) {
+                       map.m_lblk++;
+                       continue;
+               }
+
+               set_inode_flag(F2FS_I(inode), FI_DO_DEFRAG);
+
+               idx = map.m_lblk;
+               while (idx < map.m_lblk + map.m_len && cnt < blk_per_seg) {
+                       struct page *page;
+
+                       page = get_lock_data_page(inode, idx, true);
+                       if (IS_ERR(page)) {
+                               err = PTR_ERR(page);
+                               goto clear_out;
+                       }
+
+                       set_page_dirty(page);
+                       f2fs_put_page(page, 1);
+
+                       idx++;
+                       cnt++;
+                       total++;
+               }
+
+               map.m_lblk = idx;
+               map.m_len = pg_end - idx;
+
+               if (idx < pg_end && cnt < blk_per_seg)
+                       goto do_map;
+
+               clear_inode_flag(F2FS_I(inode), FI_DO_DEFRAG);
+
+               err = filemap_fdatawrite(inode->i_mapping);
+               if (err)
+                       goto out;
+       }
+clear_out:
+       clear_inode_flag(F2FS_I(inode), FI_DO_DEFRAG);
+out:
+       mutex_unlock(&inode->i_mutex);
+       if (!err)
+               range->len = (u64)total << PAGE_CACHE_SHIFT;
+       return err;
+}
+
+static int f2fs_ioc_defragment(struct file *filp, unsigned long arg)
+{
+       struct inode *inode = file_inode(filp);
+       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+       struct f2fs_defragment range;
+       int err;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (!S_ISREG(inode->i_mode))
+               return -EINVAL;
+
+       err = mnt_want_write_file(filp);
+       if (err)
+               return err;
+
+       if (f2fs_readonly(sbi->sb)) {
+               err = -EROFS;
+               goto out;
+       }
+
+       if (copy_from_user(&range, (struct f2fs_defragment __user *)arg,
+                                                       sizeof(range))) {
+               err = -EFAULT;
+               goto out;
+       }
+
+       /* verify alignment of offset & size */
+       if (range.start & (F2FS_BLKSIZE - 1) ||
+               range.len & (F2FS_BLKSIZE - 1)) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       err = f2fs_defragment_range(sbi, filp, &range);
+       if (err < 0)
+               goto out;
+
+       if (copy_to_user((struct f2fs_defragment __user *)arg, &range,
+                                                       sizeof(range)))
+               err = -EFAULT;
+out:
+       mnt_drop_write_file(filp);
+       return err;
+}
+
 long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        switch (cmd) {
@@ -1679,6 +1872,8 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return f2fs_ioc_gc(filp, arg);
        case F2FS_IOC_WRITE_CHECKPOINT:
                return f2fs_ioc_write_checkpoint(filp, arg);
+       case F2FS_IOC_DEFRAGMENT:
+               return f2fs_ioc_defragment(filp, arg);
        default:
                return -ENOTTY;
        }
This page took 0.03322 seconds and 5 git commands to generate.