ext4: Add support for FIDTRIM, a best-effort ioctl for deep discard trim
authorJP Abgrall <jpa@google.com>
Wed, 23 Jul 2014 23:55:07 +0000 (16:55 -0700)
committerJohn Stultz <john.stultz@linaro.org>
Tue, 16 Feb 2016 21:54:19 +0000 (13:54 -0800)
* What
This provides an interface for issuing an FITRIM which uses the
secure discard instead of just a discard.
Only the eMMC command is "secure", and not how the FS uses it:
due to the fact that the FS might reassign a region somewhere else,
the original deleted data will not be affected by the "trim" which only
handles un-used regions.
So we'll just call it "deep discard", and note that this is a
"best effort" cleanup.

* Why
Once in a while, We want to be able to cleanup most of the unused blocks
after erasing a bunch of files.
We don't want to constantly secure-discard via a mount option.

From an eMMC spec perspective, it tells the device to really get rid of
all the data for the specified blocks and not just put them back into the
pool of free ones (unlike the normal TRIM). The eMMC spec says the
secure trim handling must make sure the data (and metadata) is not available
anymore. A simple TRIM doesn't clear the data, it just puts blocks in the
free pool.
JEDEC Standard No. 84-A441
  7.6.9 Secure Erase
  7.6.10 Secure Trim

From an FS perspective, it is acceptable to leave some data behind.
 - directory entries related to deleted files
 - databases entries related to deleted files
 - small-file data stored in inode extents
 - blocks held by the FS waiting to be re-used (mitigated by sync).
 - blocks reassigned by the FS prior to FIDTRIM.

Change-Id: I676a1404a80130d93930c84898360f2e6fb2f81e
Signed-off-by: Geremy Condra <gcondra@google.com>
Signed-off-by: JP Abgrall <jpa@google.com>
fs/ext4/ext4.h
fs/ext4/ioctl.c
fs/ext4/mballoc.c
include/uapi/linux/fs.h

index cc7ca4e87144a540332213ce03b63e80e601f40e..5cf6d8be48dd029aca370e2cc4addefa6b514683 100644 (file)
@@ -2432,7 +2432,8 @@ extern int ext4_mb_add_groupinfo(struct super_block *sb,
                ext4_group_t i, struct ext4_group_desc *desc);
 extern int ext4_group_add_blocks(handle_t *handle, struct super_block *sb,
                                ext4_fsblk_t block, unsigned long count);
-extern int ext4_trim_fs(struct super_block *, struct fstrim_range *);
+extern int ext4_trim_fs(struct super_block *, struct fstrim_range *,
+                               unsigned long blkdev_flags);
 
 /* inode.c */
 int ext4_inode_is_fast_symlink(struct inode *inode);
index 5e872fd40e5e38655435fbcf91802244c9ce8959..95315b1f4b71c58458f0a6fa2b4d5ae2059e06a5 100644 (file)
@@ -587,11 +587,13 @@ resizefs_out:
                return err;
        }
 
+       case FIDTRIM:
        case FITRIM:
        {
                struct request_queue *q = bdev_get_queue(sb->s_bdev);
                struct fstrim_range range;
                int ret = 0;
+               int flags  = cmd == FIDTRIM ? BLKDEV_DISCARD_SECURE : 0;
 
                if (!capable(CAP_SYS_ADMIN))
                        return -EPERM;
@@ -599,13 +601,15 @@ resizefs_out:
                if (!blk_queue_discard(q))
                        return -EOPNOTSUPP;
 
+               if ((flags & BLKDEV_DISCARD_SECURE) && !blk_queue_secdiscard(q))
+                       return -EOPNOTSUPP;
                if (copy_from_user(&range, (struct fstrim_range __user *)arg,
                    sizeof(range)))
                        return -EFAULT;
 
                range.minlen = max((unsigned int)range.minlen,
                                   q->limits.discard_granularity);
-               ret = ext4_trim_fs(sb, &range);
+               ret = ext4_trim_fs(sb, &range, flags);
                if (ret < 0)
                        return ret;
 
index 61eaf74dca3794b122a9e782e87c568ac99a8a65..a235f3c20433e04ae7661af263be151e2d0fd8b7 100644 (file)
@@ -2759,7 +2759,8 @@ int ext4_mb_release(struct super_block *sb)
 }
 
 static inline int ext4_issue_discard(struct super_block *sb,
-               ext4_group_t block_group, ext4_grpblk_t cluster, int count)
+               ext4_group_t block_group, ext4_grpblk_t cluster, int count,
+               unsigned long flags)
 {
        ext4_fsblk_t discard_block;
 
@@ -2768,7 +2769,7 @@ static inline int ext4_issue_discard(struct super_block *sb,
        count = EXT4_C2B(EXT4_SB(sb), count);
        trace_ext4_discard_blocks(sb,
                        (unsigned long long) discard_block, count);
-       return sb_issue_discard(sb, discard_block, count, GFP_NOFS, 0);
+       return sb_issue_discard(sb, discard_block, count, GFP_NOFS, flags);
 }
 
 /*
@@ -2790,7 +2791,7 @@ static void ext4_free_data_callback(struct super_block *sb,
        if (test_opt(sb, DISCARD)) {
                err = ext4_issue_discard(sb, entry->efd_group,
                                         entry->efd_start_cluster,
-                                        entry->efd_count);
+                                        entry->efd_count, 0);
                if (err && err != -EOPNOTSUPP)
                        ext4_msg(sb, KERN_WARNING, "discard request in"
                                 " group:%d block:%d count:%d failed"
@@ -4844,7 +4845,8 @@ do_more:
                 * them with group lock_held
                 */
                if (test_opt(sb, DISCARD)) {
-                       err = ext4_issue_discard(sb, block_group, bit, count);
+                       err = ext4_issue_discard(sb, block_group, bit, count,
+                                                0);
                        if (err && err != -EOPNOTSUPP)
                                ext4_msg(sb, KERN_WARNING, "discard request in"
                                         " group:%d block:%d count:%lu failed"
@@ -5040,13 +5042,15 @@ error_return:
  * @count:     number of blocks to TRIM
  * @group:     alloc. group we are working with
  * @e4b:       ext4 buddy for the group
+ * @blkdev_flags: flags for the block device
  *
  * Trim "count" blocks starting at "start" in the "group". To assure that no
  * one will allocate those blocks, mark it as used in buddy bitmap. This must
  * be called with under the group lock.
  */
 static int ext4_trim_extent(struct super_block *sb, int start, int count,
-                            ext4_group_t group, struct ext4_buddy *e4b)
+                           ext4_group_t group, struct ext4_buddy *e4b,
+                           unsigned long blkdev_flags)
 __releases(bitlock)
 __acquires(bitlock)
 {
@@ -5067,7 +5071,7 @@ __acquires(bitlock)
         */
        mb_mark_used(e4b, &ex);
        ext4_unlock_group(sb, group);
-       ret = ext4_issue_discard(sb, group, start, count);
+       ret = ext4_issue_discard(sb, group, start, count, blkdev_flags);
        ext4_lock_group(sb, group);
        mb_free_blocks(NULL, e4b, start, ex.fe_len);
        return ret;
@@ -5080,6 +5084,7 @@ __acquires(bitlock)
  * @start:             first group block to examine
  * @max:               last group block to examine
  * @minblocks:         minimum extent block count
+ * @blkdev_flags:      flags for the block device
  *
  * ext4_trim_all_free walks through group's buddy bitmap searching for free
  * extents. When the free block is found, ext4_trim_extent is called to TRIM
@@ -5094,7 +5099,7 @@ __acquires(bitlock)
 static ext4_grpblk_t
 ext4_trim_all_free(struct super_block *sb, ext4_group_t group,
                   ext4_grpblk_t start, ext4_grpblk_t max,
-                  ext4_grpblk_t minblocks)
+                  ext4_grpblk_t minblocks, unsigned long blkdev_flags)
 {
        void *bitmap;
        ext4_grpblk_t next, count = 0, free_count = 0;
@@ -5127,7 +5132,8 @@ ext4_trim_all_free(struct super_block *sb, ext4_group_t group,
 
                if ((next - start) >= minblocks) {
                        ret = ext4_trim_extent(sb, start,
-                                              next - start, group, &e4b);
+                                              next - start, group, &e4b,
+                                              blkdev_flags);
                        if (ret && ret != -EOPNOTSUPP)
                                break;
                        ret = 0;
@@ -5169,6 +5175,7 @@ out:
  * ext4_trim_fs() -- trim ioctl handle function
  * @sb:                        superblock for filesystem
  * @range:             fstrim_range structure
+ * @blkdev_flags:      flags for the block device
  *
  * start:      First Byte to trim
  * len:                number of Bytes to trim from start
@@ -5177,7 +5184,8 @@ out:
  * start to start+len. For each such a group ext4_trim_all_free function
  * is invoked to trim all free space.
  */
-int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range)
+int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range,
+                       unsigned long blkdev_flags)
 {
        struct ext4_group_info *grp;
        ext4_group_t group, first_group, last_group;
@@ -5233,7 +5241,7 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range)
 
                if (grp->bb_free >= minlen) {
                        cnt = ext4_trim_all_free(sb, group, first_cluster,
-                                               end, minlen);
+                                               end, minlen, blkdev_flags);
                        if (cnt < 0) {
                                ret = cnt;
                                break;
index f15d980249b502e4638e6125f04e595f0f52035e..cd0c1a1a9ccfac53c91b8c454e9e92e26508e119 100644 (file)
@@ -160,6 +160,8 @@ struct inodes_stat_t {
 #define FITHAW         _IOWR('X', 120, int)    /* Thaw */
 #define FITRIM         _IOWR('X', 121, struct fstrim_range)    /* Trim */
 
+#define FIDTRIM        _IOWR('f', 128, struct fstrim_range)    /* Deep discard trim */
+
 #define        FS_IOC_GETFLAGS                 _IOR('f', 1, long)
 #define        FS_IOC_SETFLAGS                 _IOW('f', 2, long)
 #define        FS_IOC_GETVERSION               _IOR('v', 1, long)