diff options
Diffstat (limited to 'fs/ext4/ioctl.c')
| -rw-r--r-- | fs/ext4/ioctl.c | 92 | 
1 files changed, 91 insertions, 1 deletions
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 184e74eb3004..0c21e22acd74 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -19,6 +19,9 @@  #include <linux/delay.h>  #include "ext4_jbd2.h"  #include "ext4.h" +#include <linux/fsmap.h> +#include "fsmap.h" +#include <trace/events/ext4.h>  /**   * Swap memory between @a and @b for @len bytes. @@ -443,7 +446,7 @@ static inline unsigned long ext4_xflags_to_iflags(__u32 xflags)  	return iflags;  } -int ext4_shutdown(struct super_block *sb, unsigned long arg) +static int ext4_shutdown(struct super_block *sb, unsigned long arg)  {  	struct ext4_sb_info *sbi = EXT4_SB(sb);  	__u32 flags; @@ -489,6 +492,90 @@ int ext4_shutdown(struct super_block *sb, unsigned long arg)  	return 0;  } +struct getfsmap_info { +	struct super_block	*gi_sb; +	struct fsmap_head __user *gi_data; +	unsigned int		gi_idx; +	__u32			gi_last_flags; +}; + +static int ext4_getfsmap_format(struct ext4_fsmap *xfm, void *priv) +{ +	struct getfsmap_info *info = priv; +	struct fsmap fm; + +	trace_ext4_getfsmap_mapping(info->gi_sb, xfm); + +	info->gi_last_flags = xfm->fmr_flags; +	ext4_fsmap_from_internal(info->gi_sb, &fm, xfm); +	if (copy_to_user(&info->gi_data->fmh_recs[info->gi_idx++], &fm, +			sizeof(struct fsmap))) +		return -EFAULT; + +	return 0; +} + +static int ext4_ioc_getfsmap(struct super_block *sb, +			     struct fsmap_head __user *arg) +{ +	struct getfsmap_info info = {0}; +	struct ext4_fsmap_head xhead = {0}; +	struct fsmap_head head; +	bool aborted = false; +	int error; + +	if (copy_from_user(&head, arg, sizeof(struct fsmap_head))) +		return -EFAULT; +	if (memchr_inv(head.fmh_reserved, 0, sizeof(head.fmh_reserved)) || +	    memchr_inv(head.fmh_keys[0].fmr_reserved, 0, +		       sizeof(head.fmh_keys[0].fmr_reserved)) || +	    memchr_inv(head.fmh_keys[1].fmr_reserved, 0, +		       sizeof(head.fmh_keys[1].fmr_reserved))) +		return -EINVAL; +	/* +	 * ext4 doesn't report file extents at all, so the only valid +	 * file offsets are the magic ones (all zeroes or all ones). +	 */ +	if (head.fmh_keys[0].fmr_offset || +	    (head.fmh_keys[1].fmr_offset != 0 && +	     head.fmh_keys[1].fmr_offset != -1ULL)) +		return -EINVAL; + +	xhead.fmh_iflags = head.fmh_iflags; +	xhead.fmh_count = head.fmh_count; +	ext4_fsmap_to_internal(sb, &xhead.fmh_keys[0], &head.fmh_keys[0]); +	ext4_fsmap_to_internal(sb, &xhead.fmh_keys[1], &head.fmh_keys[1]); + +	trace_ext4_getfsmap_low_key(sb, &xhead.fmh_keys[0]); +	trace_ext4_getfsmap_high_key(sb, &xhead.fmh_keys[1]); + +	info.gi_sb = sb; +	info.gi_data = arg; +	error = ext4_getfsmap(sb, &xhead, ext4_getfsmap_format, &info); +	if (error == EXT4_QUERY_RANGE_ABORT) { +		error = 0; +		aborted = true; +	} else if (error) +		return error; + +	/* If we didn't abort, set the "last" flag in the last fmx */ +	if (!aborted && info.gi_idx) { +		info.gi_last_flags |= FMR_OF_LAST; +		if (copy_to_user(&info.gi_data->fmh_recs[info.gi_idx - 1].fmr_flags, +				 &info.gi_last_flags, +				 sizeof(info.gi_last_flags))) +			return -EFAULT; +	} + +	/* copy back header */ +	head.fmh_entries = xhead.fmh_entries; +	head.fmh_oflags = xhead.fmh_oflags; +	if (copy_to_user(arg, &head, sizeof(struct fsmap_head))) +		return -EFAULT; + +	return 0; +} +  long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)  {  	struct inode *inode = file_inode(filp); @@ -499,6 +586,8 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)  	ext4_debug("cmd = %u, arg = %lu\n", cmd, arg);  	switch (cmd) { +	case FS_IOC_GETFSMAP: +		return ext4_ioc_getfsmap(sb, (void __user *)arg);  	case EXT4_IOC_GETFLAGS:  		flags = ei->i_flags & EXT4_FL_USER_VISIBLE;  		return put_user(flags, (int __user *) arg); @@ -1007,6 +1096,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  	case EXT4_IOC_GET_ENCRYPTION_PWSALT:  	case EXT4_IOC_GET_ENCRYPTION_POLICY:  	case EXT4_IOC_SHUTDOWN: +	case FS_IOC_GETFSMAP:  		break;  	default:  		return -ENOIOCTLCMD;  |