fat: add FITRIM ioctl for FAT file system
authorWentao Wang <witallwang@gmail.com>
Wed, 22 Aug 2018 04:59:41 +0000 (21:59 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 22 Aug 2018 17:52:50 +0000 (10:52 -0700)
Add FITRIM ioctl for FAT file system

[witallwang@gmail.com: use u64s]
Link: http://lkml.kernel.org/r/87h8l37hub.fsf@mail.parknet.co.jp
[hirofumi@mail.parknet.co.jp: bug fixes, coding style fixes, add signal check]
Link: http://lkml.kernel.org/r/87fu10anhj.fsf@mail.parknet.co.jp
Signed-off-by: Wentao Wang <witallwang@gmail.com>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/fat/fat.h
fs/fat/fatent.c
fs/fat/file.c

index 8fc1093da47d38ebf354e2b96c23d6fb5f086822..154ae54a6b3aad9064c5a8e6c3aeaead991f8478 100644 (file)
@@ -357,6 +357,7 @@ extern int fat_alloc_clusters(struct inode *inode, int *cluster,
                              int nr_cluster);
 extern int fat_free_clusters(struct inode *inode, int cluster);
 extern int fat_count_free_clusters(struct super_block *sb);
+extern int fat_trim_fs(struct inode *inode, struct fstrim_range *range);
 
 /* fat/file.c */
 extern long fat_generic_ioctl(struct file *filp, unsigned int cmd,
index bac10de678cc9645af9eccd2584d0ffc548b9c93..25d43a5e8a4d930f28248a70e27721003ea8766b 100644 (file)
@@ -4,6 +4,7 @@
  */
 
 #include <linux/blkdev.h>
+#include <linux/sched/signal.h>
 #include "fat.h"
 
 struct fatent_operations {
@@ -690,3 +691,104 @@ out:
        unlock_fat(sbi);
        return err;
 }
+
+static int fat_trim_clusters(struct super_block *sb, u32 clus, u32 nr_clus)
+{
+       struct msdos_sb_info *sbi = MSDOS_SB(sb);
+       return sb_issue_discard(sb, fat_clus_to_blknr(sbi, clus),
+                               nr_clus * sbi->sec_per_clus, GFP_NOFS, 0);
+}
+
+int fat_trim_fs(struct inode *inode, struct fstrim_range *range)
+{
+       struct super_block *sb = inode->i_sb;
+       struct msdos_sb_info *sbi = MSDOS_SB(sb);
+       const struct fatent_operations *ops = sbi->fatent_ops;
+       struct fat_entry fatent;
+       u64 ent_start, ent_end, minlen, trimmed = 0;
+       u32 free = 0;
+       unsigned long reada_blocks, reada_mask, cur_block = 0;
+       int err = 0;
+
+       /*
+        * FAT data is organized as clusters, trim at the granulary of cluster.
+        *
+        * fstrim_range is in byte, convert vaules to cluster index.
+        * Treat sectors before data region as all used, not to trim them.
+        */
+       ent_start = max_t(u64, range->start>>sbi->cluster_bits, FAT_START_ENT);
+       ent_end = ent_start + (range->len >> sbi->cluster_bits) - 1;
+       minlen = range->minlen >> sbi->cluster_bits;
+
+       if (ent_start >= sbi->max_cluster || range->len < sbi->cluster_size)
+               return -EINVAL;
+       if (ent_end >= sbi->max_cluster)
+               ent_end = sbi->max_cluster - 1;
+
+       reada_blocks = FAT_READA_SIZE >> sb->s_blocksize_bits;
+       reada_mask = reada_blocks - 1;
+
+       fatent_init(&fatent);
+       lock_fat(sbi);
+       fatent_set_entry(&fatent, ent_start);
+       while (fatent.entry <= ent_end) {
+               /* readahead of fat blocks */
+               if ((cur_block & reada_mask) == 0) {
+                       unsigned long rest = sbi->fat_length - cur_block;
+                       fat_ent_reada(sb, &fatent, min(reada_blocks, rest));
+               }
+               cur_block++;
+
+               err = fat_ent_read_block(sb, &fatent);
+               if (err)
+                       goto error;
+               do {
+                       if (ops->ent_get(&fatent) == FAT_ENT_FREE) {
+                               free++;
+                       } else if (free) {
+                               if (free >= minlen) {
+                                       u32 clus = fatent.entry - free;
+
+                                       err = fat_trim_clusters(sb, clus, free);
+                                       if (err && err != -EOPNOTSUPP)
+                                               goto error;
+                                       if (!err)
+                                               trimmed += free;
+                                       err = 0;
+                               }
+                               free = 0;
+                       }
+               } while (fat_ent_next(sbi, &fatent) && fatent.entry <= ent_end);
+
+               if (fatal_signal_pending(current)) {
+                       err = -ERESTARTSYS;
+                       goto error;
+               }
+
+               if (need_resched()) {
+                       fatent_brelse(&fatent);
+                       unlock_fat(sbi);
+                       cond_resched();
+                       lock_fat(sbi);
+               }
+       }
+       /* handle scenario when tail entries are all free */
+       if (free && free >= minlen) {
+               u32 clus = fatent.entry - free;
+
+               err = fat_trim_clusters(sb, clus, free);
+               if (err && err != -EOPNOTSUPP)
+                       goto error;
+               if (!err)
+                       trimmed += free;
+               err = 0;
+       }
+
+error:
+       fatent_brelse(&fatent);
+       unlock_fat(sbi);
+
+       range->len = trimmed << sbi->cluster_bits;
+
+       return err;
+}
index 4724cc9ad65021c8fe80a3cc66783e8d01801907..4f3d72fb1e60d64a71f92e632da19ac4ac132ebc 100644 (file)
@@ -121,6 +121,37 @@ static int fat_ioctl_get_volume_id(struct inode *inode, u32 __user *user_attr)
        return put_user(sbi->vol_id, user_attr);
 }
 
+static int fat_ioctl_fitrim(struct inode *inode, unsigned long arg)
+{
+       struct super_block *sb = inode->i_sb;
+       struct fstrim_range __user *user_range;
+       struct fstrim_range range;
+       struct request_queue *q = bdev_get_queue(sb->s_bdev);
+       int err;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (!blk_queue_discard(q))
+               return -EOPNOTSUPP;
+
+       user_range = (struct fstrim_range __user *)arg;
+       if (copy_from_user(&range, user_range, sizeof(range)))
+               return -EFAULT;
+
+       range.minlen = max_t(unsigned int, range.minlen,
+                            q->limits.discard_granularity);
+
+       err = fat_trim_fs(inode, &range);
+       if (err < 0)
+               return err;
+
+       if (copy_to_user(user_range, &range, sizeof(range)))
+               return -EFAULT;
+
+       return 0;
+}
+
 long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        struct inode *inode = file_inode(filp);
@@ -133,6 +164,8 @@ long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return fat_ioctl_set_attributes(filp, user_attr);
        case FAT_IOCTL_GET_VOLUME_ID:
                return fat_ioctl_get_volume_id(inode, user_attr);
+       case FITRIM:
+               return fat_ioctl_fitrim(inode, arg);
        default:
                return -ENOTTY; /* Inappropriate ioctl for device */
        }