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