| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 |
- // 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;
- }
|