nfsd: define xattr functions to call into their vfs counterparts
authorFrank van der Linden <fllinden@amazon.com>
Tue, 23 Jun 2020 22:39:23 +0000 (22:39 +0000)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 13 Jul 2020 21:27:03 +0000 (17:27 -0400)
This adds the filehandle based functions for the xattr operations
that call in to the vfs layer to do the actual work.

Signed-off-by: Frank van der Linden <fllinden@amazon.com>
[ cel: address checkpatch.pl complaint ]
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/vfs.c
fs/nfsd/vfs.h

index d22a056da477a0b526db3f1672387d9eb7ec5979..6d2955253f73cc2c73fe8c27110cdd43967463f7 100644 (file)
@@ -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.
  */
index 3eb660ad80d1c5bc8d54f833f0b9733e7fdd1c00..a2442ebe5acf68e79aa47c48679d98f5486a2d80 100644 (file)
@@ -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 **);