diff options
| -rw-r--r-- | fs/nfsd/vfs.c | 227 | ||||
| -rw-r--r-- | fs/nfsd/vfs.h | 10 | 
2 files changed, 237 insertions, 0 deletions
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index d22a056da477..6d2955253f73 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -2065,6 +2065,233 @@ static int exp_rdonly(struct svc_rqst *rqstp, struct svc_export *exp)  	return nfsexp_flags(rqstp, exp) & NFSEXP_READONLY;  } +#ifdef CONFIG_NFSD_V4 +/* + * Helper function to translate error numbers. In the case of xattr operations, + * some error codes need to be translated outside of the standard translations. + * + * ENODATA needs to be translated to nfserr_noxattr. + * E2BIG to nfserr_xattr2big. + * + * Additionally, vfs_listxattr can return -ERANGE. This means that the + * file has too many extended attributes to retrieve inside an + * XATTR_LIST_MAX sized buffer. This is a bug in the xattr implementation: + * filesystems will allow the adding of extended attributes until they hit + * their own internal limit. This limit may be larger than XATTR_LIST_MAX. + * So, at that point, the attributes are present and valid, but can't + * be retrieved using listxattr, since the upper level xattr code enforces + * the XATTR_LIST_MAX limit. + * + * This bug means that we need to deal with listxattr returning -ERANGE. The + * best mapping is to return TOOSMALL. + */ +static __be32 +nfsd_xattr_errno(int err) +{ +	switch (err) { +	case -ENODATA: +		return nfserr_noxattr; +	case -E2BIG: +		return nfserr_xattr2big; +	case -ERANGE: +		return nfserr_toosmall; +	} +	return nfserrno(err); +} + +/* + * Retrieve the specified user extended attribute. To avoid always + * having to allocate the maximum size (since we are not getting + * a maximum size from the RPC), do a probe + alloc. Hold a reader + * lock on i_rwsem to prevent the extended attribute from changing + * size while we're doing this. + */ +__be32 +nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name, +	      void **bufp, int *lenp) +{ +	ssize_t len; +	__be32 err; +	char *buf; +	struct inode *inode; +	struct dentry *dentry; + +	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_READ); +	if (err) +		return err; + +	err = nfs_ok; +	dentry = fhp->fh_dentry; +	inode = d_inode(dentry); + +	inode_lock_shared(inode); + +	len = vfs_getxattr(dentry, name, NULL, 0); + +	/* +	 * Zero-length attribute, just return. +	 */ +	if (len == 0) { +		*bufp = NULL; +		*lenp = 0; +		goto out; +	} + +	if (len < 0) { +		err = nfsd_xattr_errno(len); +		goto out; +	} + +	if (len > *lenp) { +		err = nfserr_toosmall; +		goto out; +	} + +	buf = kvmalloc(len, GFP_KERNEL | GFP_NOFS); +	if (buf == NULL) { +		err = nfserr_jukebox; +		goto out; +	} + +	len = vfs_getxattr(dentry, name, buf, len); +	if (len <= 0) { +		kvfree(buf); +		buf = NULL; +		err = nfsd_xattr_errno(len); +	} + +	*lenp = len; +	*bufp = buf; + +out: +	inode_unlock_shared(inode); + +	return err; +} + +/* + * Retrieve the xattr names. Since we can't know how many are + * user extended attributes, we must get all attributes here, + * and have the XDR encode filter out the "user." ones. + * + * While this could always just allocate an XATTR_LIST_MAX + * buffer, that's a waste, so do a probe + allocate. To + * avoid any changes between the probe and allocate, wrap + * this in inode_lock. + */ +__be32 +nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char **bufp, +	       int *lenp) +{ +	ssize_t len; +	__be32 err; +	char *buf; +	struct inode *inode; +	struct dentry *dentry; + +	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_READ); +	if (err) +		return err; + +	dentry = fhp->fh_dentry; +	inode = d_inode(dentry); +	*lenp = 0; + +	inode_lock_shared(inode); + +	len = vfs_listxattr(dentry, NULL, 0); +	if (len <= 0) { +		err = nfsd_xattr_errno(len); +		goto out; +	} + +	if (len > XATTR_LIST_MAX) { +		err = nfserr_xattr2big; +		goto out; +	} + +	/* +	 * We're holding i_rwsem - use GFP_NOFS. +	 */ +	buf = kvmalloc(len, GFP_KERNEL | GFP_NOFS); +	if (buf == NULL) { +		err = nfserr_jukebox; +		goto out; +	} + +	len = vfs_listxattr(dentry, buf, len); +	if (len <= 0) { +		kvfree(buf); +		err = nfsd_xattr_errno(len); +		goto out; +	} + +	*lenp = len; +	*bufp = buf; + +	err = nfs_ok; +out: +	inode_unlock_shared(inode); + +	return err; +} + +/* + * Removexattr and setxattr need to call fh_lock to both lock the inode + * and set the change attribute. Since the top-level vfs_removexattr + * and vfs_setxattr calls already do their own inode_lock calls, call + * the _locked variant. Pass in a NULL pointer for delegated_inode, + * and let the client deal with NFS4ERR_DELAY (same as with e.g. + * setattr and remove). + */ +__be32 +nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name) +{ +	int err, ret; + +	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_WRITE); +	if (err) +		return err; + +	ret = fh_want_write(fhp); +	if (ret) +		return nfserrno(ret); + +	fh_lock(fhp); + +	ret = __vfs_removexattr_locked(fhp->fh_dentry, name, NULL); + +	fh_unlock(fhp); +	fh_drop_write(fhp); + +	return nfsd_xattr_errno(ret); +} + +__be32 +nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name, +	      void *buf, u32 len, u32 flags) +{ +	int err, ret; + +	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_WRITE); +	if (err) +		return err; + +	ret = fh_want_write(fhp); +	if (ret) +		return nfserrno(ret); +	fh_lock(fhp); + +	ret = __vfs_setxattr_locked(fhp->fh_dentry, name, buf, len, flags, +				    NULL); + +	fh_unlock(fhp); +	fh_drop_write(fhp); + +	return nfsd_xattr_errno(ret); +} +#endif +  /*   * Check for a user's access permissions to this inode.   */ diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h index 3eb660ad80d1..a2442ebe5acf 100644 --- a/fs/nfsd/vfs.h +++ b/fs/nfsd/vfs.h @@ -76,6 +76,16 @@ __be32		do_nfsd_create(struct svc_rqst *, struct svc_fh *,  __be32		nfsd_commit(struct svc_rqst *, struct svc_fh *,  				loff_t, unsigned long, __be32 *verf);  #endif /* CONFIG_NFSD_V3 */ +#ifdef CONFIG_NFSD_V4 +__be32		nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, +			    char *name, void **bufp, int *lenp); +__be32		nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, +			    char **bufp, int *lenp); +__be32		nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp, +			    char *name); +__be32		nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, +			    char *name, void *buf, u32 len, u32 flags); +#endif  int 		nfsd_open_break_lease(struct inode *, int);  __be32		nfsd_open(struct svc_rqst *, struct svc_fh *, umode_t,  				int, struct file **);  | 
