mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
Now that the VFS inode has a u64 i_ino field, there is no need to store a copy of the inode number in the xfs_inode structure. Introduce an I_INO() wrapper as a shortcut to the inode number so that we don't have to propagate the VFS inode everywhere. The only non-obvious part is the clearing of i_ino to 0 for RCU freeing the inode. None of this calls into VFS paths, which makes clearing the VFS inode field here just as safe as clearing the old field in the xfs_inode. Signed-off-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Signed-off-by: Carlos Maiolino <cem@kernel.org>
923 lines
21 KiB
C
923 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_platform.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 path path;
|
|
int error;
|
|
struct xfs_inode *ip;
|
|
|
|
if (cmd == XFS_IOC_FD_TO_HANDLE) {
|
|
CLASS(fd, f)(hreq->fd);
|
|
|
|
if (fd_empty(f))
|
|
return -EBADF;
|
|
path = fd_file(f)->f_path;
|
|
path_get(&path);
|
|
} 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, inode->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:
|
|
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 permflag;
|
|
struct inode *inode;
|
|
struct dentry *dentry;
|
|
fmode_t fmode;
|
|
struct path path __free(path_put) = {};
|
|
|
|
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);
|
|
path.dentry = dentry;
|
|
|
|
/* Restrict xfs_open_by_handle to directories & regular files. */
|
|
if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
|
|
return -EPERM;
|
|
|
|
#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))
|
|
return -EPERM;
|
|
|
|
if ((fmode & FMODE_WRITE) && IS_IMMUTABLE(inode))
|
|
return -EPERM;
|
|
|
|
/* Can't write directories. */
|
|
if (S_ISDIR(inode->i_mode) && (fmode & FMODE_WRITE))
|
|
return -EISDIR;
|
|
|
|
path.mnt = mntget(parfilp->f_path.mnt);
|
|
|
|
FD_PREPARE(fdf, 0, dentry_open(&path, hreq->oflags, cred));
|
|
if (fdf.err)
|
|
return fdf.err;
|
|
|
|
if (S_ISREG(inode->i_mode)) {
|
|
struct file *filp = fd_prepare_file(fdf);
|
|
|
|
filp->f_flags |= O_NOATIME;
|
|
filp->f_mode |= FMODE_NOCMTIME;
|
|
}
|
|
|
|
return fd_publish(fdf);
|
|
}
|
|
|
|
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.bufsize = round_down(bufsize, sizeof(uint32_t));
|
|
context.buffer = buffer;
|
|
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 (I_INO(ip) == 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;
|
|
}
|