From 19e049622b4806669e01fd34b6aa6677263bd3ed Mon Sep 17 00:00:00 2001 From: JP Abgrall Date: Wed, 23 Jul 2014 16:55:07 -0700 Subject: [PATCH] ANDROID: fs: ext4: Add support for FIDTRIM, a best-effort ioctl for deep discard trim * 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 Signed-off-by: JP Abgrall --- fs/ext4/ext4.h | 3 ++- fs/ext4/ioctl.c | 7 ++++++- fs/ext4/mballoc.c | 26 ++++++++++++++++---------- include/uapi/linux/fs.h | 2 ++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 58a0304566db..c4ebbcf93029 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2455,7 +2455,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); extern void ext4_process_freed_data(struct super_block *sb, tid_t commit_tid); /* inode.c */ diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 75d83471f65c..7effc0a1f64e 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -900,11 +900,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; @@ -912,13 +914,16 @@ resizefs_out: if (!blk_queue_discard(q)) return -EOPNOTSUPP; + if ((flags & BLKDEV_DISCARD_SECURE) && !blk_queue_secure_erase(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; diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 701085620cd8..462819cb87f8 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -2786,7 +2786,7 @@ 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, - struct bio **biop) + struct bio **biop, unsigned long flags) { ext4_fsblk_t discard_block; @@ -2801,7 +2801,7 @@ static inline int ext4_issue_discard(struct super_block *sb, (sector_t)count << (sb->s_blocksize_bits - 9), GFP_NOFS, 0, biop); } else - return sb_issue_discard(sb, discard_block, count, GFP_NOFS, 0); + return sb_issue_discard(sb, discard_block, count, GFP_NOFS, flags); } static void ext4_free_data_in_buddy(struct super_block *sb, @@ -2885,7 +2885,7 @@ void ext4_process_freed_data(struct super_block *sb, tid_t commit_tid) err = ext4_issue_discard(sb, entry->efd_group, entry->efd_start_cluster, entry->efd_count, - &discard_bio); + &discard_bio, 0); if (err && err != -EOPNOTSUPP) { ext4_msg(sb, KERN_WARNING, "discard request in" " group:%d block:%d count:%d failed" @@ -4920,7 +4920,7 @@ do_more: */ if (test_opt(sb, DISCARD)) { err = ext4_issue_discard(sb, block_group, bit, count, - NULL); + NULL, 0); if (err && err != -EOPNOTSUPP) ext4_msg(sb, KERN_WARNING, "discard request in" " group:%d block:%d count:%lu failed" @@ -5116,13 +5116,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) { @@ -5143,7 +5145,7 @@ __acquires(bitlock) */ mb_mark_used(e4b, &ex); ext4_unlock_group(sb, group); - ret = ext4_issue_discard(sb, group, start, count, NULL); + ret = ext4_issue_discard(sb, group, start, count, NULL, blkdev_flags); ext4_lock_group(sb, group); mb_free_blocks(NULL, e4b, start, ex.fe_len); return ret; @@ -5156,6 +5158,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 @@ -5170,7 +5173,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; @@ -5203,7 +5206,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; @@ -5245,6 +5249,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 @@ -5253,7 +5258,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; @@ -5309,7 +5315,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; diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 4199f8acbce5..b79f193ad55a 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -242,6 +242,8 @@ struct fsxattr { #define FICLONERANGE _IOW(0x94, 13, struct file_clone_range) #define FIDEDUPERANGE _IOWR(0x94, 54, struct file_dedupe_range) +#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) -- 2.20.1