mirror of
https://github.com/torvalds/linux
synced 2024-09-06 09:51:23 +00:00
![Mark Fasheh](/assets/img/avatar_default.png)
Basic vfs-level fiemap infrastructure, which sets up a new ->fiemap inode operation. Userspace can get extent information on a file via fiemap ioctl. As input, the fiemap ioctl takes a struct fiemap which includes an array of struct fiemap_extent (fm_extents). Size of the extent array is passed as fm_extent_count and number of extents returned will be written into fm_mapped_extents. Offset and length fields on the fiemap structure (fm_start, fm_length) describe a logical range which will be searched for extents. All extents returned will at least partially contain this range. The actual extent offsets and ranges returned will be unmodified from their offset and range on-disk. The fiemap ioctl returns '0' on success. On error, -1 is returned and errno is set. If errno is equal to EBADR, then fm_flags will contain those flags which were passed in which the kernel did not understand. On all other errors, the contents of fm_extents is undefined. As fiemap evolved, there have been many authors of the vfs patch. As far as I can tell, the list includes: Kalpak Shah <kalpak.shah@sun.com> Andreas Dilger <adilger@sun.com> Eric Sandeen <sandeen@redhat.com> Mark Fasheh <mfasheh@suse.com> Signed-off-by: Mark Fasheh <mfasheh@suse.com> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu> Cc: Michael Kerrisk <mtk.manpages@googlemail.com> Cc: linux-api@vger.kernel.org Cc: linux-fsdevel@vger.kernel.org
369 lines
9.1 KiB
C
369 lines
9.1 KiB
C
/*
|
|
* linux/fs/ioctl.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*/
|
|
|
|
#include <linux/syscalls.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/capability.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/security.h>
|
|
#include <linux/module.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <asm/ioctls.h>
|
|
|
|
/* So that the fiemap access checks can't overflow on 32 bit machines. */
|
|
#define FIEMAP_MAX_EXTENTS (UINT_MAX / sizeof(struct fiemap_extent))
|
|
|
|
/**
|
|
* vfs_ioctl - call filesystem specific ioctl methods
|
|
* @filp: open file to invoke ioctl method on
|
|
* @cmd: ioctl command to execute
|
|
* @arg: command-specific argument for ioctl
|
|
*
|
|
* Invokes filesystem specific ->unlocked_ioctl, if one exists; otherwise
|
|
* invokes filesystem specific ->ioctl method. If neither method exists,
|
|
* returns -ENOTTY.
|
|
*
|
|
* Returns 0 on success, -errno on error.
|
|
*/
|
|
static long vfs_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int error = -ENOTTY;
|
|
|
|
if (!filp->f_op)
|
|
goto out;
|
|
|
|
if (filp->f_op->unlocked_ioctl) {
|
|
error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
|
|
if (error == -ENOIOCTLCMD)
|
|
error = -EINVAL;
|
|
goto out;
|
|
} else if (filp->f_op->ioctl) {
|
|
lock_kernel();
|
|
error = filp->f_op->ioctl(filp->f_path.dentry->d_inode,
|
|
filp, cmd, arg);
|
|
unlock_kernel();
|
|
}
|
|
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
static int ioctl_fibmap(struct file *filp, int __user *p)
|
|
{
|
|
struct address_space *mapping = filp->f_mapping;
|
|
int res, block;
|
|
|
|
/* do we support this mess? */
|
|
if (!mapping->a_ops->bmap)
|
|
return -EINVAL;
|
|
if (!capable(CAP_SYS_RAWIO))
|
|
return -EPERM;
|
|
res = get_user(block, p);
|
|
if (res)
|
|
return res;
|
|
lock_kernel();
|
|
res = mapping->a_ops->bmap(mapping, block);
|
|
unlock_kernel();
|
|
return put_user(res, p);
|
|
}
|
|
|
|
/**
|
|
* fiemap_fill_next_extent - Fiemap helper function
|
|
* @fieinfo: Fiemap context passed into ->fiemap
|
|
* @logical: Extent logical start offset, in bytes
|
|
* @phys: Extent physical start offset, in bytes
|
|
* @len: Extent length, in bytes
|
|
* @flags: FIEMAP_EXTENT flags that describe this extent
|
|
*
|
|
* Called from file system ->fiemap callback. Will populate extent
|
|
* info as passed in via arguments and copy to user memory. On
|
|
* success, extent count on fieinfo is incremented.
|
|
*
|
|
* Returns 0 on success, -errno on error, 1 if this was the last
|
|
* extent that will fit in user array.
|
|
*/
|
|
#define SET_UNKNOWN_FLAGS (FIEMAP_EXTENT_DELALLOC)
|
|
#define SET_NO_UNMOUNTED_IO_FLAGS (FIEMAP_EXTENT_DATA_ENCRYPTED)
|
|
#define SET_NOT_ALIGNED_FLAGS (FIEMAP_EXTENT_DATA_TAIL|FIEMAP_EXTENT_DATA_INLINE)
|
|
int fiemap_fill_next_extent(struct fiemap_extent_info *fieinfo, u64 logical,
|
|
u64 phys, u64 len, u32 flags)
|
|
{
|
|
struct fiemap_extent extent;
|
|
struct fiemap_extent *dest = fieinfo->fi_extents_start;
|
|
|
|
/* only count the extents */
|
|
if (fieinfo->fi_extents_max == 0) {
|
|
fieinfo->fi_extents_mapped++;
|
|
return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0;
|
|
}
|
|
|
|
if (fieinfo->fi_extents_mapped >= fieinfo->fi_extents_max)
|
|
return 1;
|
|
|
|
if (flags & SET_UNKNOWN_FLAGS)
|
|
flags |= FIEMAP_EXTENT_UNKNOWN;
|
|
if (flags & SET_NO_UNMOUNTED_IO_FLAGS)
|
|
flags |= FIEMAP_EXTENT_ENCODED;
|
|
if (flags & SET_NOT_ALIGNED_FLAGS)
|
|
flags |= FIEMAP_EXTENT_NOT_ALIGNED;
|
|
|
|
memset(&extent, 0, sizeof(extent));
|
|
extent.fe_logical = logical;
|
|
extent.fe_physical = phys;
|
|
extent.fe_length = len;
|
|
extent.fe_flags = flags;
|
|
|
|
dest += fieinfo->fi_extents_mapped;
|
|
if (copy_to_user(dest, &extent, sizeof(extent)))
|
|
return -EFAULT;
|
|
|
|
fieinfo->fi_extents_mapped++;
|
|
if (fieinfo->fi_extents_mapped == fieinfo->fi_extents_max)
|
|
return 1;
|
|
return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0;
|
|
}
|
|
EXPORT_SYMBOL(fiemap_fill_next_extent);
|
|
|
|
/**
|
|
* fiemap_check_flags - check validity of requested flags for fiemap
|
|
* @fieinfo: Fiemap context passed into ->fiemap
|
|
* @fs_flags: Set of fiemap flags that the file system understands
|
|
*
|
|
* Called from file system ->fiemap callback. This will compute the
|
|
* intersection of valid fiemap flags and those that the fs supports. That
|
|
* value is then compared against the user supplied flags. In case of bad user
|
|
* flags, the invalid values will be written into the fieinfo structure, and
|
|
* -EBADR is returned, which tells ioctl_fiemap() to return those values to
|
|
* userspace. For this reason, a return code of -EBADR should be preserved.
|
|
*
|
|
* Returns 0 on success, -EBADR on bad flags.
|
|
*/
|
|
int fiemap_check_flags(struct fiemap_extent_info *fieinfo, u32 fs_flags)
|
|
{
|
|
u32 incompat_flags;
|
|
|
|
incompat_flags = fieinfo->fi_flags & ~(FIEMAP_FLAGS_COMPAT & fs_flags);
|
|
if (incompat_flags) {
|
|
fieinfo->fi_flags = incompat_flags;
|
|
return -EBADR;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(fiemap_check_flags);
|
|
|
|
static int fiemap_check_ranges(struct super_block *sb,
|
|
u64 start, u64 len, u64 *new_len)
|
|
{
|
|
*new_len = len;
|
|
|
|
if (len == 0)
|
|
return -EINVAL;
|
|
|
|
if (start > sb->s_maxbytes)
|
|
return -EFBIG;
|
|
|
|
/*
|
|
* Shrink request scope to what the fs can actually handle.
|
|
*/
|
|
if ((len > sb->s_maxbytes) ||
|
|
(sb->s_maxbytes - len) < start)
|
|
*new_len = sb->s_maxbytes - start;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ioctl_fiemap(struct file *filp, unsigned long arg)
|
|
{
|
|
struct fiemap fiemap;
|
|
struct fiemap_extent_info fieinfo = { 0, };
|
|
struct inode *inode = filp->f_path.dentry->d_inode;
|
|
struct super_block *sb = inode->i_sb;
|
|
u64 len;
|
|
int error;
|
|
|
|
if (!inode->i_op->fiemap)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&fiemap, (struct fiemap __user *)arg,
|
|
sizeof(struct fiemap)))
|
|
return -EFAULT;
|
|
|
|
if (fiemap.fm_extent_count > FIEMAP_MAX_EXTENTS)
|
|
return -EINVAL;
|
|
|
|
error = fiemap_check_ranges(sb, fiemap.fm_start, fiemap.fm_length,
|
|
&len);
|
|
if (error)
|
|
return error;
|
|
|
|
fieinfo.fi_flags = fiemap.fm_flags;
|
|
fieinfo.fi_extents_max = fiemap.fm_extent_count;
|
|
fieinfo.fi_extents_start = (struct fiemap_extent *)(arg + sizeof(fiemap));
|
|
|
|
if (fiemap.fm_extent_count != 0 &&
|
|
!access_ok(VERIFY_WRITE, fieinfo.fi_extents_start,
|
|
fieinfo.fi_extents_max * sizeof(struct fiemap_extent)))
|
|
return -EFAULT;
|
|
|
|
if (fieinfo.fi_flags & FIEMAP_FLAG_SYNC)
|
|
filemap_write_and_wait(inode->i_mapping);
|
|
|
|
error = inode->i_op->fiemap(inode, &fieinfo, fiemap.fm_start, len);
|
|
fiemap.fm_flags = fieinfo.fi_flags;
|
|
fiemap.fm_mapped_extents = fieinfo.fi_extents_mapped;
|
|
if (copy_to_user((char *)arg, &fiemap, sizeof(fiemap)))
|
|
error = -EFAULT;
|
|
|
|
return error;
|
|
}
|
|
|
|
static int file_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct inode *inode = filp->f_path.dentry->d_inode;
|
|
int __user *p = (int __user *)arg;
|
|
|
|
switch (cmd) {
|
|
case FIBMAP:
|
|
return ioctl_fibmap(filp, p);
|
|
case FS_IOC_FIEMAP:
|
|
return ioctl_fiemap(filp, arg);
|
|
case FIGETBSZ:
|
|
return put_user(inode->i_sb->s_blocksize, p);
|
|
case FIONREAD:
|
|
return put_user(i_size_read(inode) - filp->f_pos, p);
|
|
}
|
|
|
|
return vfs_ioctl(filp, cmd, arg);
|
|
}
|
|
|
|
static int ioctl_fionbio(struct file *filp, int __user *argp)
|
|
{
|
|
unsigned int flag;
|
|
int on, error;
|
|
|
|
error = get_user(on, argp);
|
|
if (error)
|
|
return error;
|
|
flag = O_NONBLOCK;
|
|
#ifdef __sparc__
|
|
/* SunOS compatibility item. */
|
|
if (O_NONBLOCK != O_NDELAY)
|
|
flag |= O_NDELAY;
|
|
#endif
|
|
if (on)
|
|
filp->f_flags |= flag;
|
|
else
|
|
filp->f_flags &= ~flag;
|
|
return error;
|
|
}
|
|
|
|
static int ioctl_fioasync(unsigned int fd, struct file *filp,
|
|
int __user *argp)
|
|
{
|
|
unsigned int flag;
|
|
int on, error;
|
|
|
|
error = get_user(on, argp);
|
|
if (error)
|
|
return error;
|
|
flag = on ? FASYNC : 0;
|
|
|
|
/* Did FASYNC state change ? */
|
|
if ((flag ^ filp->f_flags) & FASYNC) {
|
|
if (filp->f_op && filp->f_op->fasync) {
|
|
lock_kernel();
|
|
error = filp->f_op->fasync(fd, filp, on);
|
|
unlock_kernel();
|
|
} else
|
|
error = -ENOTTY;
|
|
}
|
|
if (error)
|
|
return error;
|
|
|
|
if (on)
|
|
filp->f_flags |= FASYNC;
|
|
else
|
|
filp->f_flags &= ~FASYNC;
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* When you add any new common ioctls to the switches above and below
|
|
* please update compat_sys_ioctl() too.
|
|
*
|
|
* do_vfs_ioctl() is not for drivers and not intended to be EXPORT_SYMBOL()'d.
|
|
* It's just a simple helper for sys_ioctl and compat_sys_ioctl.
|
|
*/
|
|
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int error = 0;
|
|
int __user *argp = (int __user *)arg;
|
|
|
|
switch (cmd) {
|
|
case FIOCLEX:
|
|
set_close_on_exec(fd, 1);
|
|
break;
|
|
|
|
case FIONCLEX:
|
|
set_close_on_exec(fd, 0);
|
|
break;
|
|
|
|
case FIONBIO:
|
|
error = ioctl_fionbio(filp, argp);
|
|
break;
|
|
|
|
case FIOASYNC:
|
|
error = ioctl_fioasync(fd, filp, argp);
|
|
break;
|
|
|
|
case FIOQSIZE:
|
|
if (S_ISDIR(filp->f_path.dentry->d_inode->i_mode) ||
|
|
S_ISREG(filp->f_path.dentry->d_inode->i_mode) ||
|
|
S_ISLNK(filp->f_path.dentry->d_inode->i_mode)) {
|
|
loff_t res =
|
|
inode_get_bytes(filp->f_path.dentry->d_inode);
|
|
error = copy_to_user((loff_t __user *)arg, &res,
|
|
sizeof(res)) ? -EFAULT : 0;
|
|
} else
|
|
error = -ENOTTY;
|
|
break;
|
|
default:
|
|
if (S_ISREG(filp->f_path.dentry->d_inode->i_mode))
|
|
error = file_ioctl(filp, cmd, arg);
|
|
else
|
|
error = vfs_ioctl(filp, cmd, arg);
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct file *filp;
|
|
int error = -EBADF;
|
|
int fput_needed;
|
|
|
|
filp = fget_light(fd, &fput_needed);
|
|
if (!filp)
|
|
goto out;
|
|
|
|
error = security_file_ioctl(filp, cmd, arg);
|
|
if (error)
|
|
goto out_fput;
|
|
|
|
error = do_vfs_ioctl(filp, fd, cmd, arg);
|
|
out_fput:
|
|
fput_light(filp, fput_needed);
|
|
out:
|
|
return error;
|
|
}
|