forked from mirrors/linux
		
	 0d113fcbc2
			
		
	
	
		0d113fcbc2
		
	
	
	
	
		
			
			XFS_IOC_FD_TO_HANDLE can grab a reference to copied ->f_path and let the file go; results in simpler control flow - cleanup is the same for both "by descriptor" and "by pathname" cases. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
		
			
				
	
	
		
			944 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			944 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Copyright (c) 2000-2005 Silicon Graphics, Inc.
 | |
|  * Copyright (c) 2022-2024 Oracle.
 | |
|  * All rights reserved.
 | |
|  */
 | |
| #include "xfs.h"
 | |
| #include "xfs_fs.h"
 | |
| #include "xfs_format.h"
 | |
| #include "xfs_log_format.h"
 | |
| #include "xfs_shared.h"
 | |
| #include "xfs_trans_resv.h"
 | |
| #include "xfs_mount.h"
 | |
| #include "xfs_bmap_btree.h"
 | |
| #include "xfs_inode.h"
 | |
| #include "xfs_error.h"
 | |
| #include "xfs_trace.h"
 | |
| #include "xfs_trans.h"
 | |
| #include "xfs_da_format.h"
 | |
| #include "xfs_da_btree.h"
 | |
| #include "xfs_attr.h"
 | |
| #include "xfs_ioctl.h"
 | |
| #include "xfs_parent.h"
 | |
| #include "xfs_handle.h"
 | |
| #include "xfs_health.h"
 | |
| #include "xfs_icache.h"
 | |
| #include "xfs_export.h"
 | |
| #include "xfs_xattr.h"
 | |
| #include "xfs_acl.h"
 | |
| 
 | |
| #include <linux/namei.h>
 | |
| 
 | |
| static inline size_t
 | |
| xfs_filehandle_fid_len(void)
 | |
| {
 | |
| 	struct xfs_handle	*handle = NULL;
 | |
| 
 | |
| 	return sizeof(struct xfs_fid) - sizeof(handle->ha_fid.fid_len);
 | |
| }
 | |
| 
 | |
| static inline size_t
 | |
| xfs_filehandle_init(
 | |
| 	struct xfs_mount	*mp,
 | |
| 	xfs_ino_t		ino,
 | |
| 	uint32_t		gen,
 | |
| 	struct xfs_handle	*handle)
 | |
| {
 | |
| 	memcpy(&handle->ha_fsid, mp->m_fixedfsid, sizeof(struct xfs_fsid));
 | |
| 
 | |
| 	handle->ha_fid.fid_len = xfs_filehandle_fid_len();
 | |
| 	handle->ha_fid.fid_pad = 0;
 | |
| 	handle->ha_fid.fid_gen = gen;
 | |
| 	handle->ha_fid.fid_ino = ino;
 | |
| 
 | |
| 	return sizeof(struct xfs_handle);
 | |
| }
 | |
| 
 | |
| static inline size_t
 | |
| xfs_fshandle_init(
 | |
| 	struct xfs_mount	*mp,
 | |
| 	struct xfs_handle	*handle)
 | |
| {
 | |
| 	memcpy(&handle->ha_fsid, mp->m_fixedfsid, sizeof(struct xfs_fsid));
 | |
| 	memset(&handle->ha_fid, 0, sizeof(handle->ha_fid));
 | |
| 
 | |
| 	return sizeof(struct xfs_fsid);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * xfs_find_handle maps from userspace xfs_fsop_handlereq structure to
 | |
|  * a file or fs handle.
 | |
|  *
 | |
|  * XFS_IOC_PATH_TO_FSHANDLE
 | |
|  *    returns fs handle for a mount point or path within that mount point
 | |
|  * XFS_IOC_FD_TO_HANDLE
 | |
|  *    returns full handle for a FD opened in user space
 | |
|  * XFS_IOC_PATH_TO_HANDLE
 | |
|  *    returns full handle for a path
 | |
|  */
 | |
| int
 | |
| xfs_find_handle(
 | |
| 	unsigned int		cmd,
 | |
| 	xfs_fsop_handlereq_t	*hreq)
 | |
| {
 | |
| 	int			hsize;
 | |
| 	xfs_handle_t		handle;
 | |
| 	struct inode		*inode;
 | |
| 	struct 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, 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;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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;
 | |
| }
 |