Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703// SPDX-License-Identifier: GPL-2.0/* * linux/fs/ioctl.c * * Copyright (C) 1991, 1992 Linus Torvalds */
#include <linux/syscalls.h>#include <linux/mm.h>#include <linux/capability.h>#include <linux/compat.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/security.h>#include <linux/export.h>#include <linux/uaccess.h>#include <linux/writeback.h>#include <linux/buffer_head.h>#include <linux/falloc.h>#include <linux/sched/signal.h>#include <linux/fiemap.h>#include <linux/mount.h>#include <linux/fscrypt.h>#include <linux/fileattr.h>
#include "internal.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 * returns -ENOTTY. * * Returns 0 on success, -errno on error. */static int vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ int error = -ENOTTY;
if (!filp->f_op->unlocked_ioctl) goto out;
error = filp->f_op->unlocked_ioctl(filp, cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; out: return error;}
static int ioctl_fibmap(struct file *filp, int __user *p){ struct inode *inode = file_inode(filp); struct super_block *sb = inode->i_sb; int error, ur_block; sector_t block;
if (!capable(CAP_SYS_RAWIO)) return -EPERM;
error = get_user(ur_block, p); if (error) return error;
if (ur_block < 0) return -EINVAL;
block = ur_block; error = bmap(inode, &block);
if (block > INT_MAX) { error = -ERANGE; pr_warn_ratelimited("[%s/%d] FS: %s File: %pD4 would truncate fibmap result\n", current->comm, task_pid_nr(current), sb->s_id, filp); }
if (error) ur_block = 0; else ur_block = block;
if (put_user(ur_block, p)) error = -EFAULT;
return error;}
/** * 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. */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 __user *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;
#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)
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_prep - check validity of requested flags for fiemap * @inode: Inode to operate on * @fieinfo: Fiemap context passed into ->fiemap * @start: Start of the mapped range * @len: Length of the mapped range, can be truncated by this function. * @supported_flags: Set of fiemap flags that the file system understands * * This function must be called from each ->fiemap instance to validate the * fiemap request against the file system parameters. * * Returns 0 on success, or a negative error on failure. */int fiemap_prep(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 *len, u32 supported_flags){ u64 maxbytes = inode->i_sb->s_maxbytes; u32 incompat_flags; int ret = 0;
if (*len == 0) return -EINVAL; if (start >= maxbytes) return -EFBIG;
/* * Shrink request scope to what the fs can actually handle. */ if (*len > maxbytes || (maxbytes - *len) < start) *len = maxbytes - start;
supported_flags |= FIEMAP_FLAG_SYNC; supported_flags &= FIEMAP_FLAGS_COMPAT; incompat_flags = fieinfo->fi_flags & ~supported_flags; if (incompat_flags) { fieinfo->fi_flags = incompat_flags; return -EBADR; }
if (fieinfo->fi_flags & FIEMAP_FLAG_SYNC) ret = filemap_write_and_wait(inode->i_mapping); return ret;}EXPORT_SYMBOL(fiemap_prep);
static int ioctl_fiemap(struct file *filp, struct fiemap __user *ufiemap){ struct fiemap fiemap; struct fiemap_extent_info fieinfo = { 0, }; struct inode *inode = file_inode(filp); int error;
if (!inode->i_op->fiemap) return -EOPNOTSUPP;
if (copy_from_user(&fiemap, ufiemap, sizeof(fiemap))) return -EFAULT;
if (fiemap.fm_extent_count > FIEMAP_MAX_EXTENTS) return -EINVAL;
fieinfo.fi_flags = fiemap.fm_flags; fieinfo.fi_extents_max = fiemap.fm_extent_count; fieinfo.fi_extents_start = ufiemap->fm_extents;
error = inode->i_op->fiemap(inode, &fieinfo, fiemap.fm_start, fiemap.fm_length);
fiemap.fm_flags = fieinfo.fi_flags; fiemap.fm_mapped_extents = fieinfo.fi_extents_mapped; if (copy_to_user(ufiemap, &fiemap, sizeof(fiemap))) error = -EFAULT;
return error;}
static int ioctl_file_clone(struct file *dst_file, unsigned long srcfd, u64 off, u64 olen, u64 destoff){ CLASS(fd, src_file)(srcfd); loff_t cloned; int ret;
if (fd_empty(src_file)) return -EBADF; cloned = vfs_clone_file_range(fd_file(src_file), off, dst_file, destoff, olen, 0); if (cloned < 0) ret = cloned; else if (olen && cloned != olen) ret = -EINVAL; else ret = 0; return ret;}
static int ioctl_file_clone_range(struct file *file, struct file_clone_range __user *argp){ struct file_clone_range args;
if (copy_from_user(&args, argp, sizeof(args))) return -EFAULT; return ioctl_file_clone(file, args.src_fd, args.src_offset, args.src_length, args.dest_offset);}
/* * This provides compatibility with legacy XFS pre-allocation ioctls * which predate the fallocate syscall. * * Only the l_start, l_len and l_whence fields of the 'struct space_resv' * are used here, rest are ignored. */static int ioctl_preallocate(struct file *filp, int mode, void __user *argp){ struct inode *inode = file_inode(filp); struct space_resv sr;
if (copy_from_user(&sr, argp, sizeof(sr))) return -EFAULT;
switch (sr.l_whence) { case SEEK_SET: break; case SEEK_CUR: sr.l_start += filp->f_pos; break; case SEEK_END: sr.l_start += i_size_read(inode); break; default: return -EINVAL; }
return vfs_fallocate(filp, mode | FALLOC_FL_KEEP_SIZE, sr.l_start, sr.l_len);}
/* on ia32 l_start is on a 32-bit boundary */#if defined CONFIG_COMPAT && defined(CONFIG_X86_64)/* just account for different alignment */static int compat_ioctl_preallocate(struct file *file, int mode, struct space_resv_32 __user *argp){ struct inode *inode = file_inode(file); struct space_resv_32 sr;
if (copy_from_user(&sr, argp, sizeof(sr))) return -EFAULT;
switch (sr.l_whence) { case SEEK_SET: break; case SEEK_CUR: sr.l_start += file->f_pos; break; case SEEK_END: sr.l_start += i_size_read(inode); break; default: return -EINVAL; }
return vfs_fallocate(file, mode | FALLOC_FL_KEEP_SIZE, sr.l_start, sr.l_len);}#endif
static int file_ioctl(struct file *filp, unsigned int cmd, int __user *p){ switch (cmd) { case FIBMAP: return ioctl_fibmap(filp, p); case FS_IOC_RESVSP: case FS_IOC_RESVSP64: return ioctl_preallocate(filp, 0, p); case FS_IOC_UNRESVSP: case FS_IOC_UNRESVSP64: return ioctl_preallocate(filp, FALLOC_FL_PUNCH_HOLE, p); case FS_IOC_ZERO_RANGE: return ioctl_preallocate(filp, FALLOC_FL_ZERO_RANGE, p); }
return -ENOIOCTLCMD;}
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 spin_lock(&filp->f_lock); if (on) filp->f_flags |= flag; else filp->f_flags &= ~flag; spin_unlock(&filp->f_lock); 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->fasync) /* fasync() adjusts filp->f_flags */ error = filp->f_op->fasync(fd, filp, on); else error = -ENOTTY; } return error < 0 ? error : 0;}
static int ioctl_fsfreeze(struct file *filp){ struct super_block *sb = file_inode(filp)->i_sb;
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) return -EPERM;
/* If filesystem doesn't support freeze feature, return. */ if (sb->s_op->freeze_fs == NULL && sb->s_op->freeze_super == NULL) return -EOPNOTSUPP;
/* Freeze */ if (sb->s_op->freeze_super) return sb->s_op->freeze_super(sb, FREEZE_HOLDER_USERSPACE, NULL); return freeze_super(sb, FREEZE_HOLDER_USERSPACE, NULL);}
static int ioctl_fsthaw(struct file *filp){ struct super_block *sb = file_inode(filp)->i_sb;
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) return -EPERM;
/* Thaw */ if (sb->s_op->thaw_super) return sb->s_op->thaw_super(sb, FREEZE_HOLDER_USERSPACE, NULL); return thaw_super(sb, FREEZE_HOLDER_USERSPACE, NULL);}
static int ioctl_file_dedupe_range(struct file *file, struct file_dedupe_range __user *argp){ struct file_dedupe_range *same = NULL; int ret; unsigned long size; u16 count;
if (get_user(count, &argp->dest_count)) { ret = -EFAULT; goto out; }
size = struct_size(same, info, count); if (size > PAGE_SIZE) { ret = -ENOMEM; goto out; }
same = memdup_user(argp, size); if (IS_ERR(same)) { ret = PTR_ERR(same); same = NULL; goto out; }
same->dest_count = count; ret = vfs_dedupe_file_range(file, same); if (ret) goto out;
ret = copy_to_user(argp, same, size); if (ret) ret = -EFAULT;
out: kfree(same); return ret;}
static int ioctl_getfsuuid(struct file *file, void __user *argp){ struct super_block *sb = file_inode(file)->i_sb; struct fsuuid2 u = { .len = sb->s_uuid_len, };
if (!sb->s_uuid_len) return -ENOTTY;
memcpy(&u.uuid[0], &sb->s_uuid, sb->s_uuid_len);
return copy_to_user(argp, &u, sizeof(u)) ? -EFAULT : 0;}
static int ioctl_get_fs_sysfs_path(struct file *file, void __user *argp){ struct super_block *sb = file_inode(file)->i_sb;
if (!strlen(sb->s_sysfs_name)) return -ENOTTY;
struct fs_sysfs_path u = {};
u.len = scnprintf(u.name, sizeof(u.name), "%s/%s", sb->s_type->name, sb->s_sysfs_name);
return copy_to_user(argp, &u, sizeof(u)) ? -EFAULT : 0;}
/* * 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. * * When you add any new common ioctls to the switches above and below, * please ensure they have compatible arguments in compat mode. * * The LSM mailing list should also be notified of any command additions or * changes, as specific LSMs may be affected. */static int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned long arg){ void __user *argp = (void __user *)arg; struct inode *inode = file_inode(filp);
switch (cmd) { case FIOCLEX: set_close_on_exec(fd, 1); return 0;
case FIONCLEX: set_close_on_exec(fd, 0); return 0;
case FIONBIO: return ioctl_fionbio(filp, argp);
case FIOASYNC: return ioctl_fioasync(fd, filp, argp);
case FIOQSIZE: if (S_ISDIR(inode->i_mode) || (S_ISREG(inode->i_mode) && !IS_ANON_FILE(inode)) || S_ISLNK(inode->i_mode)) { loff_t res = inode_get_bytes(inode); return copy_to_user(argp, &res, sizeof(res)) ? -EFAULT : 0; }
return -ENOTTY;
case FIFREEZE: return ioctl_fsfreeze(filp);
case FITHAW: return ioctl_fsthaw(filp);
case FS_IOC_FIEMAP: return ioctl_fiemap(filp, argp);
case FIGETBSZ: /* anon_bdev filesystems may not have a block size */ if (!inode->i_sb->s_blocksize) return -EINVAL;
return put_user(inode->i_sb->s_blocksize, (int __user *)argp);
case FICLONE: return ioctl_file_clone(filp, arg, 0, 0, 0);
case FICLONERANGE: return ioctl_file_clone_range(filp, argp);
case FIDEDUPERANGE: return ioctl_file_dedupe_range(filp, argp);
case FIONREAD: if (!S_ISREG(inode->i_mode) || IS_ANON_FILE(inode)) return vfs_ioctl(filp, cmd, arg);
return put_user(i_size_read(inode) - filp->f_pos, (int __user *)argp);
case FS_IOC_GETFLAGS: return ioctl_getflags(filp, argp);
case FS_IOC_SETFLAGS: return ioctl_setflags(filp, argp);
case FS_IOC_FSGETXATTR: return ioctl_fsgetxattr(filp, argp);
case FS_IOC_FSSETXATTR: return ioctl_fssetxattr(filp, argp);
case FS_IOC_GETFSUUID: return ioctl_getfsuuid(filp, argp);
case FS_IOC_GETFSSYSFSPATH: return ioctl_get_fs_sysfs_path(filp, argp);
default: if (S_ISREG(inode->i_mode) && !IS_ANON_FILE(inode)) return file_ioctl(filp, cmd, argp); break; }
return -ENOIOCTLCMD;}
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg){ CLASS(fd, f)(fd); int error;
if (fd_empty(f)) return -EBADF;
error = security_file_ioctl(fd_file(f), cmd, arg); if (error) return error;
error = do_vfs_ioctl(fd_file(f), fd, cmd, arg); if (error == -ENOIOCTLCMD) error = vfs_ioctl(fd_file(f), cmd, arg);
return error;}
#ifdef CONFIG_COMPAT/** * compat_ptr_ioctl - generic implementation of .compat_ioctl file operation * @file: The file to operate on. * @cmd: The ioctl command number. * @arg: The argument to the ioctl. * * This is not normally called as a function, but instead set in struct * file_operations as * * .compat_ioctl = compat_ptr_ioctl, * * On most architectures, the compat_ptr_ioctl() just passes all arguments * to the corresponding ->ioctl handler. The exception is arch/s390, where * compat_ptr() clears the top bit of a 32-bit pointer value, so user space * pointers to the second 2GB alias the first 2GB, as is the case for * native 32-bit s390 user space. * * The compat_ptr_ioctl() function must therefore be used only with ioctl * functions that either ignore the argument or pass a pointer to a * compatible data type. * * If any ioctl command handled by fops->unlocked_ioctl passes a plain * integer instead of a pointer, or any of the passed data types * is incompatible between 32-bit and 64-bit architectures, a proper * handler is required instead of compat_ptr_ioctl. */long compat_ptr_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ if (!file->f_op->unlocked_ioctl) return -ENOIOCTLCMD;
return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)compat_ptr(arg));}EXPORT_SYMBOL(compat_ptr_ioctl);
COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, compat_ulong_t, arg){ CLASS(fd, f)(fd); int error;
if (fd_empty(f)) return -EBADF;
error = security_file_ioctl_compat(fd_file(f), cmd, arg); if (error) return error;
switch (cmd) { /* FICLONE takes an int argument, so don't use compat_ptr() */ case FICLONE: error = ioctl_file_clone(fd_file(f), arg, 0, 0, 0); break;
#if defined(CONFIG_X86_64) /* these get messy on amd64 due to alignment differences */ case FS_IOC_RESVSP_32: case FS_IOC_RESVSP64_32: error = compat_ioctl_preallocate(fd_file(f), 0, compat_ptr(arg)); break; case FS_IOC_UNRESVSP_32: case FS_IOC_UNRESVSP64_32: error = compat_ioctl_preallocate(fd_file(f), FALLOC_FL_PUNCH_HOLE, compat_ptr(arg)); break; case FS_IOC_ZERO_RANGE_32: error = compat_ioctl_preallocate(fd_file(f), FALLOC_FL_ZERO_RANGE, compat_ptr(arg)); break;#endif
/* * These access 32-bit values anyway so no further handling is * necessary. */ case FS_IOC32_GETFLAGS: case FS_IOC32_SETFLAGS: cmd = (cmd == FS_IOC32_GETFLAGS) ? FS_IOC_GETFLAGS : FS_IOC_SETFLAGS; fallthrough; /* * everything else in do_vfs_ioctl() takes either a compatible * pointer argument or no argument -- call it with a modified * argument. */ default: error = do_vfs_ioctl(fd_file(f), fd, cmd, (unsigned long)compat_ptr(arg)); if (error != -ENOIOCTLCMD) break;
if (fd_file(f)->f_op->compat_ioctl) error = fd_file(f)->f_op->compat_ioctl(fd_file(f), cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; break; } return error;}#endif