mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
16c096a3f5
* 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>
512 lines
12 KiB
C
512 lines
12 KiB
C
/*
|
|
* linux/fs/ext4/ioctl.c
|
|
*
|
|
* Copyright (C) 1993, 1994, 1995
|
|
* Remy Card (card@masi.ibp.fr)
|
|
* Laboratoire MASI - Institut Blaise Pascal
|
|
* Universite Pierre et Marie Curie (Paris VI)
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/jbd2.h>
|
|
#include <linux/capability.h>
|
|
#include <linux/time.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/file.h>
|
|
#include <asm/uaccess.h>
|
|
#include "ext4_jbd2.h"
|
|
#include "ext4.h"
|
|
|
|
#define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1)
|
|
|
|
long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct inode *inode = filp->f_dentry->d_inode;
|
|
struct super_block *sb = inode->i_sb;
|
|
struct ext4_inode_info *ei = EXT4_I(inode);
|
|
unsigned int flags;
|
|
|
|
ext4_debug("cmd = %u, arg = %lu\n", cmd, arg);
|
|
|
|
switch (cmd) {
|
|
case EXT4_IOC_GETFLAGS:
|
|
ext4_get_inode_flags(ei);
|
|
flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
|
|
return put_user(flags, (int __user *) arg);
|
|
case EXT4_IOC_SETFLAGS: {
|
|
handle_t *handle = NULL;
|
|
int err, migrate = 0;
|
|
struct ext4_iloc iloc;
|
|
unsigned int oldflags, mask, i;
|
|
unsigned int jflag;
|
|
|
|
if (!inode_owner_or_capable(inode))
|
|
return -EACCES;
|
|
|
|
if (get_user(flags, (int __user *) arg))
|
|
return -EFAULT;
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
return err;
|
|
|
|
flags = ext4_mask_flags(inode->i_mode, flags);
|
|
|
|
err = -EPERM;
|
|
mutex_lock(&inode->i_mutex);
|
|
/* Is it quota file? Do not allow user to mess with it */
|
|
if (IS_NOQUOTA(inode))
|
|
goto flags_out;
|
|
|
|
oldflags = ei->i_flags;
|
|
|
|
/* The JOURNAL_DATA flag is modifiable only by root */
|
|
jflag = flags & EXT4_JOURNAL_DATA_FL;
|
|
|
|
/*
|
|
* The IMMUTABLE and APPEND_ONLY flags can only be changed by
|
|
* the relevant capability.
|
|
*
|
|
* This test looks nicer. Thanks to Pauline Middelink
|
|
*/
|
|
if ((flags ^ oldflags) & (EXT4_APPEND_FL | EXT4_IMMUTABLE_FL)) {
|
|
if (!capable(CAP_LINUX_IMMUTABLE))
|
|
goto flags_out;
|
|
}
|
|
|
|
/*
|
|
* The JOURNAL_DATA flag can only be changed by
|
|
* the relevant capability.
|
|
*/
|
|
if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL)) {
|
|
if (!capable(CAP_SYS_RESOURCE))
|
|
goto flags_out;
|
|
}
|
|
if (oldflags & EXT4_EXTENTS_FL) {
|
|
/* We don't support clearning extent flags */
|
|
if (!(flags & EXT4_EXTENTS_FL)) {
|
|
err = -EOPNOTSUPP;
|
|
goto flags_out;
|
|
}
|
|
} else if (flags & EXT4_EXTENTS_FL) {
|
|
/* migrate the file */
|
|
migrate = 1;
|
|
flags &= ~EXT4_EXTENTS_FL;
|
|
}
|
|
|
|
if (flags & EXT4_EOFBLOCKS_FL) {
|
|
/* we don't support adding EOFBLOCKS flag */
|
|
if (!(oldflags & EXT4_EOFBLOCKS_FL)) {
|
|
err = -EOPNOTSUPP;
|
|
goto flags_out;
|
|
}
|
|
} else if (oldflags & EXT4_EOFBLOCKS_FL)
|
|
ext4_truncate(inode);
|
|
|
|
handle = ext4_journal_start(inode, 1);
|
|
if (IS_ERR(handle)) {
|
|
err = PTR_ERR(handle);
|
|
goto flags_out;
|
|
}
|
|
if (IS_SYNC(inode))
|
|
ext4_handle_sync(handle);
|
|
err = ext4_reserve_inode_write(handle, inode, &iloc);
|
|
if (err)
|
|
goto flags_err;
|
|
|
|
for (i = 0, mask = 1; i < 32; i++, mask <<= 1) {
|
|
if (!(mask & EXT4_FL_USER_MODIFIABLE))
|
|
continue;
|
|
if (mask & flags)
|
|
ext4_set_inode_flag(inode, i);
|
|
else
|
|
ext4_clear_inode_flag(inode, i);
|
|
}
|
|
|
|
ext4_set_inode_flags(inode);
|
|
inode->i_ctime = ext4_current_time(inode);
|
|
|
|
err = ext4_mark_iloc_dirty(handle, inode, &iloc);
|
|
flags_err:
|
|
ext4_journal_stop(handle);
|
|
if (err)
|
|
goto flags_out;
|
|
|
|
if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL))
|
|
err = ext4_change_inode_journal_flag(inode, jflag);
|
|
if (err)
|
|
goto flags_out;
|
|
if (migrate)
|
|
err = ext4_ext_migrate(inode);
|
|
flags_out:
|
|
mutex_unlock(&inode->i_mutex);
|
|
mnt_drop_write_file(filp);
|
|
return err;
|
|
}
|
|
case EXT4_IOC_GETVERSION:
|
|
case EXT4_IOC_GETVERSION_OLD:
|
|
return put_user(inode->i_generation, (int __user *) arg);
|
|
case EXT4_IOC_SETVERSION:
|
|
case EXT4_IOC_SETVERSION_OLD: {
|
|
handle_t *handle;
|
|
struct ext4_iloc iloc;
|
|
__u32 generation;
|
|
int err;
|
|
|
|
if (!inode_owner_or_capable(inode))
|
|
return -EPERM;
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
return err;
|
|
if (get_user(generation, (int __user *) arg)) {
|
|
err = -EFAULT;
|
|
goto setversion_out;
|
|
}
|
|
|
|
mutex_lock(&inode->i_mutex);
|
|
handle = ext4_journal_start(inode, 1);
|
|
if (IS_ERR(handle)) {
|
|
err = PTR_ERR(handle);
|
|
goto unlock_out;
|
|
}
|
|
err = ext4_reserve_inode_write(handle, inode, &iloc);
|
|
if (err == 0) {
|
|
inode->i_ctime = ext4_current_time(inode);
|
|
inode->i_generation = generation;
|
|
err = ext4_mark_iloc_dirty(handle, inode, &iloc);
|
|
}
|
|
ext4_journal_stop(handle);
|
|
|
|
unlock_out:
|
|
mutex_unlock(&inode->i_mutex);
|
|
setversion_out:
|
|
mnt_drop_write_file(filp);
|
|
return err;
|
|
}
|
|
case EXT4_IOC_GROUP_EXTEND: {
|
|
ext4_fsblk_t n_blocks_count;
|
|
int err, err2=0;
|
|
|
|
err = ext4_resize_begin(sb);
|
|
if (err)
|
|
return err;
|
|
|
|
if (get_user(n_blocks_count, (__u32 __user *)arg)) {
|
|
err = -EFAULT;
|
|
goto group_extend_out;
|
|
}
|
|
|
|
if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"Online resizing not supported with bigalloc");
|
|
err = -EOPNOTSUPP;
|
|
goto group_extend_out;
|
|
}
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
goto group_extend_out;
|
|
|
|
err = ext4_group_extend(sb, EXT4_SB(sb)->s_es, n_blocks_count);
|
|
if (EXT4_SB(sb)->s_journal) {
|
|
jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal);
|
|
err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal);
|
|
jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal);
|
|
}
|
|
if (err == 0)
|
|
err = err2;
|
|
mnt_drop_write_file(filp);
|
|
group_extend_out:
|
|
ext4_resize_end(sb);
|
|
return err;
|
|
}
|
|
|
|
case EXT4_IOC_MOVE_EXT: {
|
|
struct move_extent me;
|
|
struct file *donor_filp;
|
|
int err;
|
|
|
|
if (!(filp->f_mode & FMODE_READ) ||
|
|
!(filp->f_mode & FMODE_WRITE))
|
|
return -EBADF;
|
|
|
|
if (copy_from_user(&me,
|
|
(struct move_extent __user *)arg, sizeof(me)))
|
|
return -EFAULT;
|
|
me.moved_len = 0;
|
|
|
|
donor_filp = fget(me.donor_fd);
|
|
if (!donor_filp)
|
|
return -EBADF;
|
|
|
|
if (!(donor_filp->f_mode & FMODE_WRITE)) {
|
|
err = -EBADF;
|
|
goto mext_out;
|
|
}
|
|
|
|
if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"Online defrag not supported with bigalloc");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
goto mext_out;
|
|
|
|
err = ext4_move_extents(filp, donor_filp, me.orig_start,
|
|
me.donor_start, me.len, &me.moved_len);
|
|
mnt_drop_write_file(filp);
|
|
|
|
if (copy_to_user((struct move_extent __user *)arg,
|
|
&me, sizeof(me)))
|
|
err = -EFAULT;
|
|
mext_out:
|
|
fput(donor_filp);
|
|
return err;
|
|
}
|
|
|
|
case EXT4_IOC_GROUP_ADD: {
|
|
struct ext4_new_group_data input;
|
|
int err, err2=0;
|
|
|
|
err = ext4_resize_begin(sb);
|
|
if (err)
|
|
return err;
|
|
|
|
if (copy_from_user(&input, (struct ext4_new_group_input __user *)arg,
|
|
sizeof(input))) {
|
|
err = -EFAULT;
|
|
goto group_add_out;
|
|
}
|
|
|
|
if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"Online resizing not supported with bigalloc");
|
|
err = -EOPNOTSUPP;
|
|
goto group_add_out;
|
|
}
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
goto group_add_out;
|
|
|
|
err = ext4_group_add(sb, &input);
|
|
if (EXT4_SB(sb)->s_journal) {
|
|
jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal);
|
|
err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal);
|
|
jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal);
|
|
}
|
|
if (err == 0)
|
|
err = err2;
|
|
mnt_drop_write_file(filp);
|
|
group_add_out:
|
|
ext4_resize_end(sb);
|
|
return err;
|
|
}
|
|
|
|
case EXT4_IOC_MIGRATE:
|
|
{
|
|
int err;
|
|
if (!inode_owner_or_capable(inode))
|
|
return -EACCES;
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
return err;
|
|
/*
|
|
* inode_mutex prevent write and truncate on the file.
|
|
* Read still goes through. We take i_data_sem in
|
|
* ext4_ext_swap_inode_data before we switch the
|
|
* inode format to prevent read.
|
|
*/
|
|
mutex_lock(&(inode->i_mutex));
|
|
err = ext4_ext_migrate(inode);
|
|
mutex_unlock(&(inode->i_mutex));
|
|
mnt_drop_write_file(filp);
|
|
return err;
|
|
}
|
|
|
|
case EXT4_IOC_ALLOC_DA_BLKS:
|
|
{
|
|
int err;
|
|
if (!inode_owner_or_capable(inode))
|
|
return -EACCES;
|
|
|
|
err = mnt_want_write_file(filp);
|
|
if (err)
|
|
return err;
|
|
err = ext4_alloc_da_blocks(inode);
|
|
mnt_drop_write_file(filp);
|
|
return err;
|
|
}
|
|
|
|
case EXT4_IOC_RESIZE_FS: {
|
|
ext4_fsblk_t n_blocks_count;
|
|
struct super_block *sb = inode->i_sb;
|
|
int err = 0, err2 = 0;
|
|
|
|
if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"Online resizing not (yet) supported with bigalloc");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (EXT4_HAS_INCOMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_INCOMPAT_META_BG)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"Online resizing not (yet) supported with meta_bg");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (copy_from_user(&n_blocks_count, (__u64 __user *)arg,
|
|
sizeof(__u64))) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (n_blocks_count > MAX_32_NUM &&
|
|
!EXT4_HAS_INCOMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_INCOMPAT_64BIT)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"File system only supports 32-bit block numbers");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
err = ext4_resize_begin(sb);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mnt_want_write(filp->f_path.mnt);
|
|
if (err)
|
|
goto resizefs_out;
|
|
|
|
err = ext4_resize_fs(sb, n_blocks_count);
|
|
if (EXT4_SB(sb)->s_journal) {
|
|
jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal);
|
|
err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal);
|
|
jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal);
|
|
}
|
|
if (err == 0)
|
|
err = err2;
|
|
mnt_drop_write(filp->f_path.mnt);
|
|
resizefs_out:
|
|
ext4_resize_end(sb);
|
|
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;
|
|
|
|
if (!blk_queue_discard(q))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
|
|
EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
|
|
ext4_msg(sb, KERN_ERR,
|
|
"FITRIM not supported with bigalloc");
|
|
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, flags);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (copy_to_user((struct fstrim_range __user *)arg, &range,
|
|
sizeof(range)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
default:
|
|
return -ENOTTY;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
/* These are just misnamed, they actually get/put from/to user an int */
|
|
switch (cmd) {
|
|
case EXT4_IOC32_GETFLAGS:
|
|
cmd = EXT4_IOC_GETFLAGS;
|
|
break;
|
|
case EXT4_IOC32_SETFLAGS:
|
|
cmd = EXT4_IOC_SETFLAGS;
|
|
break;
|
|
case EXT4_IOC32_GETVERSION:
|
|
cmd = EXT4_IOC_GETVERSION;
|
|
break;
|
|
case EXT4_IOC32_SETVERSION:
|
|
cmd = EXT4_IOC_SETVERSION;
|
|
break;
|
|
case EXT4_IOC32_GROUP_EXTEND:
|
|
cmd = EXT4_IOC_GROUP_EXTEND;
|
|
break;
|
|
case EXT4_IOC32_GETVERSION_OLD:
|
|
cmd = EXT4_IOC_GETVERSION_OLD;
|
|
break;
|
|
case EXT4_IOC32_SETVERSION_OLD:
|
|
cmd = EXT4_IOC_SETVERSION_OLD;
|
|
break;
|
|
case EXT4_IOC32_GETRSVSZ:
|
|
cmd = EXT4_IOC_GETRSVSZ;
|
|
break;
|
|
case EXT4_IOC32_SETRSVSZ:
|
|
cmd = EXT4_IOC_SETRSVSZ;
|
|
break;
|
|
case EXT4_IOC32_GROUP_ADD: {
|
|
struct compat_ext4_new_group_input __user *uinput;
|
|
struct ext4_new_group_input input;
|
|
mm_segment_t old_fs;
|
|
int err;
|
|
|
|
uinput = compat_ptr(arg);
|
|
err = get_user(input.group, &uinput->group);
|
|
err |= get_user(input.block_bitmap, &uinput->block_bitmap);
|
|
err |= get_user(input.inode_bitmap, &uinput->inode_bitmap);
|
|
err |= get_user(input.inode_table, &uinput->inode_table);
|
|
err |= get_user(input.blocks_count, &uinput->blocks_count);
|
|
err |= get_user(input.reserved_blocks,
|
|
&uinput->reserved_blocks);
|
|
if (err)
|
|
return -EFAULT;
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
err = ext4_ioctl(file, EXT4_IOC_GROUP_ADD,
|
|
(unsigned long) &input);
|
|
set_fs(old_fs);
|
|
return err;
|
|
}
|
|
case EXT4_IOC_MOVE_EXT:
|
|
case FITRIM:
|
|
case EXT4_IOC_RESIZE_FS:
|
|
break;
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
return ext4_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
|
|
}
|
|
#endif
|