mirror of
https://github.com/torvalds/linux.git
synced 2024-12-19 01:23:20 +00:00
88a2f6468d
We want the compiler to see that fdput() on empty instance is a no-op. The emptiness check is that file reference is NULL, while fdput() is "fput() if FDPUT_FPUT is present in flags". The reason why fdput() on empty instance is a no-op is something compiler can't see - it's that we never generate instances with NULL file reference combined with non-zero flags. It's not that hard to deal with - the real primitives behind fdget() et.al. are returning an unsigned long value, unpacked by (inlined) __to_fd() into the current struct file * + int. The lower bits are used to store flags, while the rest encodes the pointer. Linus suggested that keeping this unsigned long around with the extractions done by inlined accessors should generate a sane code and that turns out to be the case. Namely, turning struct fd into a struct-wrapped unsinged long, with fd_empty(f) => unlikely(f.word == 0) fd_file(f) => (struct file *)(f.word & ~3) fdput(f) => if (f.word & 1) fput(fd_file(f)) ends up with compiler doing the right thing. The cost is the patch footprint, of course - we need to switch f.file to fd_file(f) all over the tree, and it's not doable with simple search and replace; there are false positives, etc. Note that the sole member of that structure is an opaque unsigned long - all accesses should be done via wrappers and I don't want to use a name that would invite manual casts to file pointers, etc. The value of that member is equal either to (unsigned long)p | flags, p being an address of some struct file instance, or to 0 for an empty fd. For now the new predicate (fd_empty(f)) has no users; all the existing checks have form (!fd_file(f)). We will convert to fd_empty() use later; here we only define it (and tell the compiler that it's unlikely to return true). This commit only deals with representation change; there will be followups. Reviewed-by: Christian Brauner <brauner@kernel.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
947 lines
21 KiB
C
947 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2000-2005 Silicon Graphics, Inc.
|
|
* Copyright (c) 2022-2024 Oracle.
|
|
* All rights reserved.
|
|
*/
|
|
#include "xfs.h"
|
|
#include "xfs_fs.h"
|
|
#include "xfs_format.h"
|
|
#include "xfs_log_format.h"
|
|
#include "xfs_shared.h"
|
|
#include "xfs_trans_resv.h"
|
|
#include "xfs_mount.h"
|
|
#include "xfs_bmap_btree.h"
|
|
#include "xfs_inode.h"
|
|
#include "xfs_error.h"
|
|
#include "xfs_trace.h"
|
|
#include "xfs_trans.h"
|
|
#include "xfs_da_format.h"
|
|
#include "xfs_da_btree.h"
|
|
#include "xfs_attr.h"
|
|
#include "xfs_ioctl.h"
|
|
#include "xfs_parent.h"
|
|
#include "xfs_handle.h"
|
|
#include "xfs_health.h"
|
|
#include "xfs_icache.h"
|
|
#include "xfs_export.h"
|
|
#include "xfs_xattr.h"
|
|
#include "xfs_acl.h"
|
|
|
|
#include <linux/namei.h>
|
|
|
|
static inline size_t
|
|
xfs_filehandle_fid_len(void)
|
|
{
|
|
struct xfs_handle *handle = NULL;
|
|
|
|
return sizeof(struct xfs_fid) - sizeof(handle->ha_fid.fid_len);
|
|
}
|
|
|
|
static inline size_t
|
|
xfs_filehandle_init(
|
|
struct xfs_mount *mp,
|
|
xfs_ino_t ino,
|
|
uint32_t gen,
|
|
struct xfs_handle *handle)
|
|
{
|
|
memcpy(&handle->ha_fsid, mp->m_fixedfsid, sizeof(struct xfs_fsid));
|
|
|
|
handle->ha_fid.fid_len = xfs_filehandle_fid_len();
|
|
handle->ha_fid.fid_pad = 0;
|
|
handle->ha_fid.fid_gen = gen;
|
|
handle->ha_fid.fid_ino = ino;
|
|
|
|
return sizeof(struct xfs_handle);
|
|
}
|
|
|
|
static inline size_t
|
|
xfs_fshandle_init(
|
|
struct xfs_mount *mp,
|
|
struct xfs_handle *handle)
|
|
{
|
|
memcpy(&handle->ha_fsid, mp->m_fixedfsid, sizeof(struct xfs_fsid));
|
|
memset(&handle->ha_fid, 0, sizeof(handle->ha_fid));
|
|
|
|
return sizeof(struct xfs_fsid);
|
|
}
|
|
|
|
/*
|
|
* xfs_find_handle maps from userspace xfs_fsop_handlereq structure to
|
|
* a file or fs handle.
|
|
*
|
|
* XFS_IOC_PATH_TO_FSHANDLE
|
|
* returns fs handle for a mount point or path within that mount point
|
|
* XFS_IOC_FD_TO_HANDLE
|
|
* returns full handle for a FD opened in user space
|
|
* XFS_IOC_PATH_TO_HANDLE
|
|
* returns full handle for a path
|
|
*/
|
|
int
|
|
xfs_find_handle(
|
|
unsigned int cmd,
|
|
xfs_fsop_handlereq_t *hreq)
|
|
{
|
|
int hsize;
|
|
xfs_handle_t handle;
|
|
struct inode *inode;
|
|
struct fd f = EMPTY_FD;
|
|
struct path path;
|
|
int error;
|
|
struct xfs_inode *ip;
|
|
|
|
if (cmd == XFS_IOC_FD_TO_HANDLE) {
|
|
f = fdget(hreq->fd);
|
|
if (!fd_file(f))
|
|
return -EBADF;
|
|
inode = file_inode(fd_file(f));
|
|
} else {
|
|
error = user_path_at(AT_FDCWD, hreq->path, 0, &path);
|
|
if (error)
|
|
return error;
|
|
inode = d_inode(path.dentry);
|
|
}
|
|
ip = XFS_I(inode);
|
|
|
|
/*
|
|
* We can only generate handles for inodes residing on a XFS filesystem,
|
|
* and only for regular files, directories or symbolic links.
|
|
*/
|
|
error = -EINVAL;
|
|
if (inode->i_sb->s_magic != XFS_SB_MAGIC)
|
|
goto out_put;
|
|
|
|
error = -EBADF;
|
|
if (!S_ISREG(inode->i_mode) &&
|
|
!S_ISDIR(inode->i_mode) &&
|
|
!S_ISLNK(inode->i_mode))
|
|
goto out_put;
|
|
|
|
|
|
memcpy(&handle.ha_fsid, ip->i_mount->m_fixedfsid, sizeof(xfs_fsid_t));
|
|
|
|
if (cmd == XFS_IOC_PATH_TO_FSHANDLE)
|
|
hsize = xfs_fshandle_init(ip->i_mount, &handle);
|
|
else
|
|
hsize = xfs_filehandle_init(ip->i_mount, ip->i_ino,
|
|
inode->i_generation, &handle);
|
|
|
|
error = -EFAULT;
|
|
if (copy_to_user(hreq->ohandle, &handle, hsize) ||
|
|
copy_to_user(hreq->ohandlen, &hsize, sizeof(__s32)))
|
|
goto out_put;
|
|
|
|
error = 0;
|
|
|
|
out_put:
|
|
if (cmd == XFS_IOC_FD_TO_HANDLE)
|
|
fdput(f);
|
|
else
|
|
path_put(&path);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* No need to do permission checks on the various pathname components
|
|
* as the handle operations are privileged.
|
|
*/
|
|
STATIC int
|
|
xfs_handle_acceptable(
|
|
void *context,
|
|
struct dentry *dentry)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* Convert handle already copied to kernel space into a dentry. */
|
|
static struct dentry *
|
|
xfs_khandle_to_dentry(
|
|
struct file *file,
|
|
struct xfs_handle *handle)
|
|
{
|
|
struct xfs_fid64 fid = {
|
|
.ino = handle->ha_fid.fid_ino,
|
|
.gen = handle->ha_fid.fid_gen,
|
|
};
|
|
|
|
/*
|
|
* Only allow handle opens under a directory.
|
|
*/
|
|
if (!S_ISDIR(file_inode(file)->i_mode))
|
|
return ERR_PTR(-ENOTDIR);
|
|
|
|
if (handle->ha_fid.fid_len != xfs_filehandle_fid_len())
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return exportfs_decode_fh(file->f_path.mnt, (struct fid *)&fid, 3,
|
|
FILEID_INO32_GEN | XFS_FILEID_TYPE_64FLAG,
|
|
xfs_handle_acceptable, NULL);
|
|
}
|
|
|
|
/* Convert handle already copied to kernel space into an xfs_inode. */
|
|
static struct xfs_inode *
|
|
xfs_khandle_to_inode(
|
|
struct file *file,
|
|
struct xfs_handle *handle)
|
|
{
|
|
struct xfs_inode *ip = XFS_I(file_inode(file));
|
|
struct xfs_mount *mp = ip->i_mount;
|
|
struct inode *inode;
|
|
|
|
if (!S_ISDIR(VFS_I(ip)->i_mode))
|
|
return ERR_PTR(-ENOTDIR);
|
|
|
|
if (handle->ha_fid.fid_len != xfs_filehandle_fid_len())
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
inode = xfs_nfs_get_inode(mp->m_super, handle->ha_fid.fid_ino,
|
|
handle->ha_fid.fid_gen);
|
|
if (IS_ERR(inode))
|
|
return ERR_CAST(inode);
|
|
|
|
return XFS_I(inode);
|
|
}
|
|
|
|
/*
|
|
* Convert userspace handle data into a dentry.
|
|
*/
|
|
struct dentry *
|
|
xfs_handle_to_dentry(
|
|
struct file *parfilp,
|
|
void __user *uhandle,
|
|
u32 hlen)
|
|
{
|
|
xfs_handle_t handle;
|
|
|
|
if (hlen != sizeof(xfs_handle_t))
|
|
return ERR_PTR(-EINVAL);
|
|
if (copy_from_user(&handle, uhandle, hlen))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
return xfs_khandle_to_dentry(parfilp, &handle);
|
|
}
|
|
|
|
STATIC struct dentry *
|
|
xfs_handlereq_to_dentry(
|
|
struct file *parfilp,
|
|
xfs_fsop_handlereq_t *hreq)
|
|
{
|
|
return xfs_handle_to_dentry(parfilp, hreq->ihandle, hreq->ihandlen);
|
|
}
|
|
|
|
int
|
|
xfs_open_by_handle(
|
|
struct file *parfilp,
|
|
xfs_fsop_handlereq_t *hreq)
|
|
{
|
|
const struct cred *cred = current_cred();
|
|
int error;
|
|
int fd;
|
|
int permflag;
|
|
struct file *filp;
|
|
struct inode *inode;
|
|
struct dentry *dentry;
|
|
fmode_t fmode;
|
|
struct path path;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
dentry = xfs_handlereq_to_dentry(parfilp, hreq);
|
|
if (IS_ERR(dentry))
|
|
return PTR_ERR(dentry);
|
|
inode = d_inode(dentry);
|
|
|
|
/* Restrict xfs_open_by_handle to directories & regular files. */
|
|
if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) {
|
|
error = -EPERM;
|
|
goto out_dput;
|
|
}
|
|
|
|
#if BITS_PER_LONG != 32
|
|
hreq->oflags |= O_LARGEFILE;
|
|
#endif
|
|
|
|
permflag = hreq->oflags;
|
|
fmode = OPEN_FMODE(permflag);
|
|
if ((!(permflag & O_APPEND) || (permflag & O_TRUNC)) &&
|
|
(fmode & FMODE_WRITE) && IS_APPEND(inode)) {
|
|
error = -EPERM;
|
|
goto out_dput;
|
|
}
|
|
|
|
if ((fmode & FMODE_WRITE) && IS_IMMUTABLE(inode)) {
|
|
error = -EPERM;
|
|
goto out_dput;
|
|
}
|
|
|
|
/* Can't write directories. */
|
|
if (S_ISDIR(inode->i_mode) && (fmode & FMODE_WRITE)) {
|
|
error = -EISDIR;
|
|
goto out_dput;
|
|
}
|
|
|
|
fd = get_unused_fd_flags(0);
|
|
if (fd < 0) {
|
|
error = fd;
|
|
goto out_dput;
|
|
}
|
|
|
|
path.mnt = parfilp->f_path.mnt;
|
|
path.dentry = dentry;
|
|
filp = dentry_open(&path, hreq->oflags, cred);
|
|
dput(dentry);
|
|
if (IS_ERR(filp)) {
|
|
put_unused_fd(fd);
|
|
return PTR_ERR(filp);
|
|
}
|
|
|
|
if (S_ISREG(inode->i_mode)) {
|
|
filp->f_flags |= O_NOATIME;
|
|
filp->f_mode |= FMODE_NOCMTIME;
|
|
}
|
|
|
|
fd_install(fd, filp);
|
|
return fd;
|
|
|
|
out_dput:
|
|
dput(dentry);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
xfs_readlink_by_handle(
|
|
struct file *parfilp,
|
|
xfs_fsop_handlereq_t *hreq)
|
|
{
|
|
struct dentry *dentry;
|
|
__u32 olen;
|
|
int error;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
dentry = xfs_handlereq_to_dentry(parfilp, hreq);
|
|
if (IS_ERR(dentry))
|
|
return PTR_ERR(dentry);
|
|
|
|
/* Restrict this handle operation to symlinks only. */
|
|
if (!d_is_symlink(dentry)) {
|
|
error = -EINVAL;
|
|
goto out_dput;
|
|
}
|
|
|
|
if (copy_from_user(&olen, hreq->ohandlen, sizeof(__u32))) {
|
|
error = -EFAULT;
|
|
goto out_dput;
|
|
}
|
|
|
|
error = vfs_readlink(dentry, hreq->ohandle, olen);
|
|
|
|
out_dput:
|
|
dput(dentry);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Format an attribute and copy it out to the user's buffer.
|
|
* Take care to check values and protect against them changing later,
|
|
* we may be reading them directly out of a user buffer.
|
|
*/
|
|
static void
|
|
xfs_ioc_attr_put_listent(
|
|
struct xfs_attr_list_context *context,
|
|
int flags,
|
|
unsigned char *name,
|
|
int namelen,
|
|
void *value,
|
|
int valuelen)
|
|
{
|
|
struct xfs_attrlist *alist = context->buffer;
|
|
struct xfs_attrlist_ent *aep;
|
|
int arraytop;
|
|
|
|
ASSERT(!context->seen_enough);
|
|
ASSERT(context->count >= 0);
|
|
ASSERT(context->count < (ATTR_MAX_VALUELEN/8));
|
|
ASSERT(context->firstu >= sizeof(*alist));
|
|
ASSERT(context->firstu <= context->bufsize);
|
|
|
|
/*
|
|
* Only list entries in the right namespace.
|
|
*/
|
|
if (context->attr_filter != (flags & XFS_ATTR_NSP_ONDISK_MASK))
|
|
return;
|
|
|
|
arraytop = sizeof(*alist) +
|
|
context->count * sizeof(alist->al_offset[0]);
|
|
|
|
/* decrement by the actual bytes used by the attr */
|
|
context->firstu -= round_up(offsetof(struct xfs_attrlist_ent, a_name) +
|
|
namelen + 1, sizeof(uint32_t));
|
|
if (context->firstu < arraytop) {
|
|
trace_xfs_attr_list_full(context);
|
|
alist->al_more = 1;
|
|
context->seen_enough = 1;
|
|
return;
|
|
}
|
|
|
|
aep = context->buffer + context->firstu;
|
|
aep->a_valuelen = valuelen;
|
|
memcpy(aep->a_name, name, namelen);
|
|
aep->a_name[namelen] = 0;
|
|
alist->al_offset[context->count++] = context->firstu;
|
|
alist->al_count = context->count;
|
|
trace_xfs_attr_list_add(context);
|
|
}
|
|
|
|
static unsigned int
|
|
xfs_attr_filter(
|
|
u32 ioc_flags)
|
|
{
|
|
if (ioc_flags & XFS_IOC_ATTR_ROOT)
|
|
return XFS_ATTR_ROOT;
|
|
if (ioc_flags & XFS_IOC_ATTR_SECURE)
|
|
return XFS_ATTR_SECURE;
|
|
return 0;
|
|
}
|
|
|
|
static inline enum xfs_attr_update
|
|
xfs_xattr_flags(
|
|
u32 ioc_flags,
|
|
void *value)
|
|
{
|
|
if (!value)
|
|
return XFS_ATTRUPDATE_REMOVE;
|
|
if (ioc_flags & XFS_IOC_ATTR_CREATE)
|
|
return XFS_ATTRUPDATE_CREATE;
|
|
if (ioc_flags & XFS_IOC_ATTR_REPLACE)
|
|
return XFS_ATTRUPDATE_REPLACE;
|
|
return XFS_ATTRUPDATE_UPSERT;
|
|
}
|
|
|
|
int
|
|
xfs_ioc_attr_list(
|
|
struct xfs_inode *dp,
|
|
void __user *ubuf,
|
|
size_t bufsize,
|
|
int flags,
|
|
struct xfs_attrlist_cursor __user *ucursor)
|
|
{
|
|
struct xfs_attr_list_context context = { };
|
|
struct xfs_attrlist *alist;
|
|
void *buffer;
|
|
int error;
|
|
|
|
if (bufsize < sizeof(struct xfs_attrlist) ||
|
|
bufsize > XFS_XATTR_LIST_MAX)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Reject flags, only allow namespaces.
|
|
*/
|
|
if (flags & ~(XFS_IOC_ATTR_ROOT | XFS_IOC_ATTR_SECURE))
|
|
return -EINVAL;
|
|
if (flags == (XFS_IOC_ATTR_ROOT | XFS_IOC_ATTR_SECURE))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Validate the cursor.
|
|
*/
|
|
if (copy_from_user(&context.cursor, ucursor, sizeof(context.cursor)))
|
|
return -EFAULT;
|
|
if (context.cursor.pad1 || context.cursor.pad2)
|
|
return -EINVAL;
|
|
if (!context.cursor.initted &&
|
|
(context.cursor.hashval || context.cursor.blkno ||
|
|
context.cursor.offset))
|
|
return -EINVAL;
|
|
|
|
buffer = kvzalloc(bufsize, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Initialize the output buffer.
|
|
*/
|
|
context.dp = dp;
|
|
context.resynch = 1;
|
|
context.attr_filter = xfs_attr_filter(flags);
|
|
context.buffer = buffer;
|
|
context.bufsize = round_down(bufsize, sizeof(uint32_t));
|
|
context.firstu = context.bufsize;
|
|
context.put_listent = xfs_ioc_attr_put_listent;
|
|
|
|
alist = context.buffer;
|
|
alist->al_count = 0;
|
|
alist->al_more = 0;
|
|
alist->al_offset[0] = context.bufsize;
|
|
|
|
error = xfs_attr_list(&context);
|
|
if (error)
|
|
goto out_free;
|
|
|
|
if (copy_to_user(ubuf, buffer, bufsize) ||
|
|
copy_to_user(ucursor, &context.cursor, sizeof(context.cursor)))
|
|
error = -EFAULT;
|
|
out_free:
|
|
kvfree(buffer);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
xfs_attrlist_by_handle(
|
|
struct file *parfilp,
|
|
struct xfs_fsop_attrlist_handlereq __user *p)
|
|
{
|
|
struct xfs_fsop_attrlist_handlereq al_hreq;
|
|
struct dentry *dentry;
|
|
int error = -ENOMEM;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
if (copy_from_user(&al_hreq, p, sizeof(al_hreq)))
|
|
return -EFAULT;
|
|
|
|
dentry = xfs_handlereq_to_dentry(parfilp, &al_hreq.hreq);
|
|
if (IS_ERR(dentry))
|
|
return PTR_ERR(dentry);
|
|
|
|
error = xfs_ioc_attr_list(XFS_I(d_inode(dentry)), al_hreq.buffer,
|
|
al_hreq.buflen, al_hreq.flags, &p->pos);
|
|
dput(dentry);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
xfs_attrmulti_attr_get(
|
|
struct inode *inode,
|
|
unsigned char *name,
|
|
unsigned char __user *ubuf,
|
|
uint32_t *len,
|
|
uint32_t flags)
|
|
{
|
|
struct xfs_da_args args = {
|
|
.dp = XFS_I(inode),
|
|
.attr_filter = xfs_attr_filter(flags),
|
|
.name = name,
|
|
.namelen = strlen(name),
|
|
.valuelen = *len,
|
|
};
|
|
int error;
|
|
|
|
if (*len > XFS_XATTR_SIZE_MAX)
|
|
return -EINVAL;
|
|
|
|
error = xfs_attr_get(&args);
|
|
if (error)
|
|
goto out_kfree;
|
|
|
|
*len = args.valuelen;
|
|
if (copy_to_user(ubuf, args.value, args.valuelen))
|
|
error = -EFAULT;
|
|
|
|
out_kfree:
|
|
kvfree(args.value);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
xfs_attrmulti_attr_set(
|
|
struct inode *inode,
|
|
unsigned char *name,
|
|
const unsigned char __user *ubuf,
|
|
uint32_t len,
|
|
uint32_t flags)
|
|
{
|
|
struct xfs_da_args args = {
|
|
.dp = XFS_I(inode),
|
|
.attr_filter = xfs_attr_filter(flags),
|
|
.name = name,
|
|
.namelen = strlen(name),
|
|
};
|
|
int error;
|
|
|
|
if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
|
|
return -EPERM;
|
|
|
|
if (ubuf) {
|
|
if (len > XFS_XATTR_SIZE_MAX)
|
|
return -EINVAL;
|
|
args.value = memdup_user(ubuf, len);
|
|
if (IS_ERR(args.value))
|
|
return PTR_ERR(args.value);
|
|
args.valuelen = len;
|
|
}
|
|
|
|
error = xfs_attr_change(&args, xfs_xattr_flags(flags, args.value));
|
|
if (!error && (flags & XFS_IOC_ATTR_ROOT))
|
|
xfs_forget_acl(inode, name);
|
|
kfree(args.value);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
xfs_ioc_attrmulti_one(
|
|
struct file *parfilp,
|
|
struct inode *inode,
|
|
uint32_t opcode,
|
|
void __user *uname,
|
|
void __user *value,
|
|
uint32_t *len,
|
|
uint32_t flags)
|
|
{
|
|
unsigned char *name;
|
|
int error;
|
|
|
|
if ((flags & XFS_IOC_ATTR_ROOT) && (flags & XFS_IOC_ATTR_SECURE))
|
|
return -EINVAL;
|
|
|
|
name = strndup_user(uname, MAXNAMELEN);
|
|
if (IS_ERR(name))
|
|
return PTR_ERR(name);
|
|
|
|
switch (opcode) {
|
|
case ATTR_OP_GET:
|
|
error = xfs_attrmulti_attr_get(inode, name, value, len, flags);
|
|
break;
|
|
case ATTR_OP_REMOVE:
|
|
value = NULL;
|
|
*len = 0;
|
|
fallthrough;
|
|
case ATTR_OP_SET:
|
|
error = mnt_want_write_file(parfilp);
|
|
if (error)
|
|
break;
|
|
error = xfs_attrmulti_attr_set(inode, name, value, *len, flags);
|
|
mnt_drop_write_file(parfilp);
|
|
break;
|
|
default:
|
|
error = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
kfree(name);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
xfs_attrmulti_by_handle(
|
|
struct file *parfilp,
|
|
void __user *arg)
|
|
{
|
|
int error;
|
|
xfs_attr_multiop_t *ops;
|
|
xfs_fsop_attrmulti_handlereq_t am_hreq;
|
|
struct dentry *dentry;
|
|
unsigned int i, size;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
if (copy_from_user(&am_hreq, arg, sizeof(xfs_fsop_attrmulti_handlereq_t)))
|
|
return -EFAULT;
|
|
|
|
/* overflow check */
|
|
if (am_hreq.opcount >= INT_MAX / sizeof(xfs_attr_multiop_t))
|
|
return -E2BIG;
|
|
|
|
dentry = xfs_handlereq_to_dentry(parfilp, &am_hreq.hreq);
|
|
if (IS_ERR(dentry))
|
|
return PTR_ERR(dentry);
|
|
|
|
error = -E2BIG;
|
|
size = am_hreq.opcount * sizeof(xfs_attr_multiop_t);
|
|
if (!size || size > 16 * PAGE_SIZE)
|
|
goto out_dput;
|
|
|
|
ops = memdup_user(am_hreq.ops, size);
|
|
if (IS_ERR(ops)) {
|
|
error = PTR_ERR(ops);
|
|
goto out_dput;
|
|
}
|
|
|
|
error = 0;
|
|
for (i = 0; i < am_hreq.opcount; i++) {
|
|
ops[i].am_error = xfs_ioc_attrmulti_one(parfilp,
|
|
d_inode(dentry), ops[i].am_opcode,
|
|
ops[i].am_attrname, ops[i].am_attrvalue,
|
|
&ops[i].am_length, ops[i].am_flags);
|
|
}
|
|
|
|
if (copy_to_user(am_hreq.ops, ops, size))
|
|
error = -EFAULT;
|
|
|
|
kfree(ops);
|
|
out_dput:
|
|
dput(dentry);
|
|
return error;
|
|
}
|
|
|
|
struct xfs_getparents_ctx {
|
|
struct xfs_attr_list_context context;
|
|
struct xfs_getparents_by_handle gph;
|
|
|
|
/* File to target */
|
|
struct xfs_inode *ip;
|
|
|
|
/* Internal buffer where we format records */
|
|
void *krecords;
|
|
|
|
/* Last record filled out */
|
|
struct xfs_getparents_rec *lastrec;
|
|
|
|
unsigned int count;
|
|
};
|
|
|
|
static inline unsigned int
|
|
xfs_getparents_rec_sizeof(
|
|
unsigned int namelen)
|
|
{
|
|
return round_up(sizeof(struct xfs_getparents_rec) + namelen + 1,
|
|
sizeof(uint64_t));
|
|
}
|
|
|
|
static void
|
|
xfs_getparents_put_listent(
|
|
struct xfs_attr_list_context *context,
|
|
int flags,
|
|
unsigned char *name,
|
|
int namelen,
|
|
void *value,
|
|
int valuelen)
|
|
{
|
|
struct xfs_getparents_ctx *gpx =
|
|
container_of(context, struct xfs_getparents_ctx, context);
|
|
struct xfs_inode *ip = context->dp;
|
|
struct xfs_mount *mp = ip->i_mount;
|
|
struct xfs_getparents *gp = &gpx->gph.gph_request;
|
|
struct xfs_getparents_rec *gpr = gpx->krecords + context->firstu;
|
|
unsigned short reclen =
|
|
xfs_getparents_rec_sizeof(namelen);
|
|
xfs_ino_t ino;
|
|
uint32_t gen;
|
|
int error;
|
|
|
|
if (!(flags & XFS_ATTR_PARENT))
|
|
return;
|
|
|
|
error = xfs_parent_from_attr(mp, flags, name, namelen, value, valuelen,
|
|
&ino, &gen);
|
|
if (error) {
|
|
xfs_inode_mark_sick(ip, XFS_SICK_INO_PARENT);
|
|
context->seen_enough = -EFSCORRUPTED;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We found a parent pointer, but we've filled up the buffer. Signal
|
|
* to the caller that we did /not/ reach the end of the parent pointer
|
|
* recordset.
|
|
*/
|
|
if (context->firstu > context->bufsize - reclen) {
|
|
context->seen_enough = 1;
|
|
return;
|
|
}
|
|
|
|
/* Format the parent pointer directly into the caller buffer. */
|
|
gpr->gpr_reclen = reclen;
|
|
xfs_filehandle_init(mp, ino, gen, &gpr->gpr_parent);
|
|
memcpy(gpr->gpr_name, name, namelen);
|
|
gpr->gpr_name[namelen] = 0;
|
|
|
|
trace_xfs_getparents_put_listent(ip, gp, context, gpr);
|
|
|
|
context->firstu += reclen;
|
|
gpx->count++;
|
|
gpx->lastrec = gpr;
|
|
}
|
|
|
|
/* Expand the last record to fill the rest of the caller's buffer. */
|
|
static inline void
|
|
xfs_getparents_expand_lastrec(
|
|
struct xfs_getparents_ctx *gpx)
|
|
{
|
|
struct xfs_getparents *gp = &gpx->gph.gph_request;
|
|
struct xfs_getparents_rec *gpr = gpx->lastrec;
|
|
|
|
if (!gpx->lastrec)
|
|
gpr = gpx->krecords;
|
|
|
|
gpr->gpr_reclen = gp->gp_bufsize - ((void *)gpr - gpx->krecords);
|
|
|
|
trace_xfs_getparents_expand_lastrec(gpx->ip, gp, &gpx->context, gpr);
|
|
}
|
|
|
|
/* Retrieve the parent pointers for a given inode. */
|
|
STATIC int
|
|
xfs_getparents(
|
|
struct xfs_getparents_ctx *gpx)
|
|
{
|
|
struct xfs_getparents *gp = &gpx->gph.gph_request;
|
|
struct xfs_inode *ip = gpx->ip;
|
|
struct xfs_mount *mp = ip->i_mount;
|
|
size_t bufsize;
|
|
int error;
|
|
|
|
/* Check size of buffer requested by user */
|
|
if (gp->gp_bufsize > XFS_XATTR_LIST_MAX)
|
|
return -ENOMEM;
|
|
if (gp->gp_bufsize < xfs_getparents_rec_sizeof(1))
|
|
return -EINVAL;
|
|
|
|
if (gp->gp_iflags & ~XFS_GETPARENTS_IFLAGS_ALL)
|
|
return -EINVAL;
|
|
if (gp->gp_reserved)
|
|
return -EINVAL;
|
|
|
|
bufsize = round_down(gp->gp_bufsize, sizeof(uint64_t));
|
|
gpx->krecords = kvzalloc(bufsize, GFP_KERNEL);
|
|
if (!gpx->krecords) {
|
|
bufsize = min(bufsize, PAGE_SIZE);
|
|
gpx->krecords = kvzalloc(bufsize, GFP_KERNEL);
|
|
if (!gpx->krecords)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
gpx->context.dp = ip;
|
|
gpx->context.resynch = 1;
|
|
gpx->context.put_listent = xfs_getparents_put_listent;
|
|
gpx->context.bufsize = bufsize;
|
|
/* firstu is used to track the bytes filled in the buffer */
|
|
gpx->context.firstu = 0;
|
|
|
|
/* Copy the cursor provided by caller */
|
|
memcpy(&gpx->context.cursor, &gp->gp_cursor,
|
|
sizeof(struct xfs_attrlist_cursor));
|
|
gpx->count = 0;
|
|
gp->gp_oflags = 0;
|
|
|
|
trace_xfs_getparents_begin(ip, gp, &gpx->context.cursor);
|
|
|
|
error = xfs_attr_list(&gpx->context);
|
|
if (error)
|
|
goto out_free_buf;
|
|
if (gpx->context.seen_enough < 0) {
|
|
error = gpx->context.seen_enough;
|
|
goto out_free_buf;
|
|
}
|
|
xfs_getparents_expand_lastrec(gpx);
|
|
|
|
/* Update the caller with the current cursor position */
|
|
memcpy(&gp->gp_cursor, &gpx->context.cursor,
|
|
sizeof(struct xfs_attrlist_cursor));
|
|
|
|
/* Is this the root directory? */
|
|
if (ip->i_ino == mp->m_sb.sb_rootino)
|
|
gp->gp_oflags |= XFS_GETPARENTS_OFLAG_ROOT;
|
|
|
|
if (gpx->context.seen_enough == 0) {
|
|
/*
|
|
* If we did not run out of buffer space, then we reached the
|
|
* end of the pptr recordset, so set the DONE flag.
|
|
*/
|
|
gp->gp_oflags |= XFS_GETPARENTS_OFLAG_DONE;
|
|
} else if (gpx->count == 0) {
|
|
/*
|
|
* If we ran out of buffer space before copying any parent
|
|
* pointers at all, the caller's buffer was too short. Tell
|
|
* userspace that, erm, the message is too long.
|
|
*/
|
|
error = -EMSGSIZE;
|
|
goto out_free_buf;
|
|
}
|
|
|
|
trace_xfs_getparents_end(ip, gp, &gpx->context.cursor);
|
|
|
|
ASSERT(gpx->context.firstu <= gpx->gph.gph_request.gp_bufsize);
|
|
|
|
/* Copy the records to userspace. */
|
|
if (copy_to_user(u64_to_user_ptr(gpx->gph.gph_request.gp_buffer),
|
|
gpx->krecords, gpx->context.firstu))
|
|
error = -EFAULT;
|
|
|
|
out_free_buf:
|
|
kvfree(gpx->krecords);
|
|
gpx->krecords = NULL;
|
|
return error;
|
|
}
|
|
|
|
/* Retrieve the parents of this file and pass them back to userspace. */
|
|
int
|
|
xfs_ioc_getparents(
|
|
struct file *file,
|
|
struct xfs_getparents __user *ureq)
|
|
{
|
|
struct xfs_getparents_ctx gpx = {
|
|
.ip = XFS_I(file_inode(file)),
|
|
};
|
|
struct xfs_getparents *kreq = &gpx.gph.gph_request;
|
|
struct xfs_mount *mp = gpx.ip->i_mount;
|
|
int error;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
if (!xfs_has_parent(mp))
|
|
return -EOPNOTSUPP;
|
|
if (copy_from_user(kreq, ureq, sizeof(*kreq)))
|
|
return -EFAULT;
|
|
|
|
error = xfs_getparents(&gpx);
|
|
if (error)
|
|
return error;
|
|
|
|
if (copy_to_user(ureq, kreq, sizeof(*kreq)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Retrieve the parents of this file handle and pass them back to userspace. */
|
|
int
|
|
xfs_ioc_getparents_by_handle(
|
|
struct file *file,
|
|
struct xfs_getparents_by_handle __user *ureq)
|
|
{
|
|
struct xfs_getparents_ctx gpx = { };
|
|
struct xfs_inode *ip = XFS_I(file_inode(file));
|
|
struct xfs_mount *mp = ip->i_mount;
|
|
struct xfs_getparents_by_handle *kreq = &gpx.gph;
|
|
struct xfs_handle *handle = &kreq->gph_handle;
|
|
int error;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
if (!xfs_has_parent(mp))
|
|
return -EOPNOTSUPP;
|
|
if (copy_from_user(kreq, ureq, sizeof(*kreq)))
|
|
return -EFAULT;
|
|
|
|
/*
|
|
* We don't use exportfs_decode_fh because it does too much work here.
|
|
* If the handle refers to a directory, the exportfs code will walk
|
|
* upwards through the directory tree to connect the dentries to the
|
|
* root directory dentry. For GETPARENTS we don't care about that
|
|
* because we're not actually going to open a file descriptor; we only
|
|
* want to open an inode and read its parent pointers.
|
|
*
|
|
* Note that xfs_scrub uses GETPARENTS to log that it will try to fix a
|
|
* corrupted file's metadata. For this usecase we would really rather
|
|
* userspace single-step the path reconstruction to avoid loops or
|
|
* other strange things if the directory tree is corrupt.
|
|
*/
|
|
gpx.ip = xfs_khandle_to_inode(file, handle);
|
|
if (IS_ERR(gpx.ip))
|
|
return PTR_ERR(gpx.ip);
|
|
|
|
error = xfs_getparents(&gpx);
|
|
if (error)
|
|
goto out_rele;
|
|
|
|
if (copy_to_user(ureq, kreq, sizeof(*kreq)))
|
|
error = -EFAULT;
|
|
|
|
out_rele:
|
|
xfs_irele(gpx.ip);
|
|
return error;
|
|
}
|