#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);
}
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);
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:
path_put(&path);
return error;
}
STATIC int
xfs_handle_acceptable(
void *context,
struct dentry *dentry)
{
return 1;
}
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,
};
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);
}
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);
}
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;
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;
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);
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;
}
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);
if (context->attr_filter != (flags & XFS_ATTR_NSP_ONDISK_MASK))
return;
arraytop = sizeof(*alist) +
context->count * sizeof(alist->al_offset[0]);
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;
if (flags & ~(XFS_IOC_ATTR_ROOT | XFS_IOC_ATTR_SECURE))
return -EINVAL;
if (flags == (XFS_IOC_ATTR_ROOT | XFS_IOC_ATTR_SECURE))
return -EINVAL;
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;
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;
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;
struct xfs_inode *ip;
void *krecords;
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;
}
if (context->firstu > context->bufsize - reclen) {
context->seen_enough = 1;
return;
}
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;
}
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);
}
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;
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;
gpx->context.firstu = 0;
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);
memcpy(&gp->gp_cursor, &gpx->context.cursor,
sizeof(struct xfs_attrlist_cursor));
if (ip->i_ino == mp->m_sb.sb_rootino)
gp->gp_oflags |= XFS_GETPARENTS_OFLAG_ROOT;
if (gpx->context.seen_enough == 0) {
gp->gp_oflags |= XFS_GETPARENTS_OFLAG_DONE;
} else if (gpx->count == 0) {
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);
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;
}
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;
}
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;
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;
}