summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS3
-rw-r--r--fs/lockd/svc.c1
-rw-r--r--fs/lockd/trace.h1
-rw-r--r--fs/lockd/xdr.h2
-rw-r--r--fs/namei.c2
-rw-r--r--fs/nfs/nfs4file.c1
-rw-r--r--fs/nfs/super.c25
-rw-r--r--fs/nfs_common/nfs_ssc.c114
-rw-r--r--fs/nfsd/blocklayout.c1
-rw-r--r--fs/nfsd/blocklayoutxdr.c11
-rw-r--r--fs/nfsd/export.c8
-rw-r--r--fs/nfsd/export.h3
-rw-r--r--fs/nfsd/filecache.c1
-rw-r--r--fs/nfsd/flexfilelayout.c24
-rw-r--r--fs/nfsd/flexfilelayoutxdr.c9
-rw-r--r--fs/nfsd/flexfilelayoutxdr.h8
-rw-r--r--fs/nfsd/localio.c10
-rw-r--r--fs/nfsd/lockd.c4
-rw-r--r--fs/nfsd/nfs2acl.c47
-rw-r--r--fs/nfsd/nfs3acl.c1
-rw-r--r--fs/nfsd/nfs3proc.c78
-rw-r--r--fs/nfsd/nfs3xdr.c3
-rw-r--r--fs/nfsd/nfs4acl.c1
-rw-r--r--fs/nfsd/nfs4callback.c8
-rw-r--r--fs/nfsd/nfs4ctl.h83
-rw-r--r--fs/nfsd/nfs4idmap.c1
-rw-r--r--fs/nfsd/nfs4layouts.c1
-rw-r--r--fs/nfsd/nfs4proc.c435
-rw-r--r--fs/nfsd/nfs4recover.c1
-rw-r--r--fs/nfsd/nfs4state.c264
-rw-r--r--fs/nfsd/nfs4xdr.c132
-rw-r--r--fs/nfsd/nfscache.c1
-rw-r--r--fs/nfsd/nfsctl.c2
-rw-r--r--fs/nfsd/nfsd.h236
-rw-r--r--fs/nfsd/nfserr.h158
-rw-r--r--fs/nfsd/nfsfh.c72
-rw-r--r--fs/nfsd/nfsfh.h28
-rw-r--r--fs/nfsd/nfsproc.c20
-rw-r--r--fs/nfsd/nfssvc.c8
-rw-r--r--fs/nfsd/nfsxdr.c1
-rw-r--r--fs/nfsd/state.h44
-rw-r--r--fs/nfsd/vfs.c298
-rw-r--r--fs/nfsd/vfs.h39
-rw-r--r--fs/nfsd/xdr3.h2
-rw-r--r--fs/nfsd/xdr4.h152
-rw-r--r--fs/nfsd/xdr4cb.h20
-rw-r--r--include/linux/nfs.h55
-rw-r--r--include/linux/nfs3.h8
-rw-r--r--include/linux/nfs4.h6
-rw-r--r--include/linux/nfs_fh.h63
-rw-r--r--include/linux/nfs_ssc.h69
-rw-r--r--include/linux/nfsd_ssc.h38
-rw-r--r--include/linux/nfslocalio.h11
-rw-r--r--include/trace/misc/nfs.h1
-rw-r--r--net/sunrpc/svcsock.c83
-rw-r--r--net/sunrpc/xprtsock.c66
56 files changed, 1670 insertions, 1094 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 6215fcb07770..aa45acd54ed6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14186,7 +14186,8 @@ L: linux-nfs@vger.kernel.org
S: Supported
P: Documentation/filesystems/nfs/nfsd-maintainer-entry-profile.rst
B: https://bugzilla.kernel.org
-T: git git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git nfsd-testing
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git nfsd-next
F: Documentation/filesystems/nfs/
F: fs/lockd/
F: fs/nfs_common/
diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c
index ee90e743064a..f0e1a58c9106 100644
--- a/fs/lockd/svc.c
+++ b/fs/lockd/svc.c
@@ -36,7 +36,6 @@
#include <net/ip.h>
#include <net/addrconf.h>
#include <net/ipv6.h>
-#include <linux/nfs.h>
#include "lockd.h"
#include "netns.h"
diff --git a/fs/lockd/trace.h b/fs/lockd/trace.h
index a11d04e8c835..1f79955ea0f5 100644
--- a/fs/lockd/trace.h
+++ b/fs/lockd/trace.h
@@ -7,7 +7,6 @@
#include <linux/tracepoint.h>
#include <linux/crc32.h>
-#include <linux/nfs.h>
#include "lockd.h"
diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h
index a1126cca98c6..56b9796aa39d 100644
--- a/fs/lockd/xdr.h
+++ b/fs/lockd/xdr.h
@@ -10,7 +10,7 @@
#include <linux/fs.h>
#include <linux/filelock.h>
-#include <linux/nfs.h>
+#include <linux/nfs_fh.h>
#include <linux/sunrpc/xdr.h>
#define SM_MAXSTRLEN 1024
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3ef..d95249dd527c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -5211,6 +5211,8 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
error = vfs_create(mnt_idmap(path->mnt), path->dentry, mode, NULL);
if (!error)
error = vfs_open(path, file);
+ if (!error)
+ file->f_mode |= FMODE_CREATED;
}
if (unlikely(error))
return ERR_PTR(error);
diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
index 6401f6363f75..9a434f5dda8d 100644
--- a/fs/nfs/nfs4file.c
+++ b/fs/nfs/nfs4file.c
@@ -402,6 +402,7 @@ static void __nfs42_ssc_close(struct file *filep)
}
static const struct nfs4_ssc_client_ops nfs4_ssc_clnt_ops_tbl = {
+ .owner = THIS_MODULE,
.sco_open = __nfs42_ssc_open,
.sco_close = __nfs42_ssc_close,
};
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index cb19f1540d98..23292680adbd 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -58,7 +58,6 @@
#include <linux/rcupdate.h>
#include <linux/uaccess.h>
-#include <linux/nfs_ssc.h>
#include <uapi/linux/tls.h>
@@ -92,12 +91,6 @@ const struct super_operations nfs_sops = {
};
EXPORT_SYMBOL_GPL(nfs_sops);
-#ifdef CONFIG_NFS_V4_2
-static const struct nfs_ssc_client_ops nfs_ssc_clnt_ops_tbl = {
- .sco_sb_deactive = nfs_sb_deactive,
-};
-#endif
-
#if IS_ENABLED(CONFIG_NFS_V4)
static int __init register_nfs4_fs(void)
{
@@ -119,18 +112,6 @@ static void unregister_nfs4_fs(void)
}
#endif
-#ifdef CONFIG_NFS_V4_2
-static void nfs_ssc_register_ops(void)
-{
- nfs_ssc_register(&nfs_ssc_clnt_ops_tbl);
-}
-
-static void nfs_ssc_unregister_ops(void)
-{
- nfs_ssc_unregister(&nfs_ssc_clnt_ops_tbl);
-}
-#endif /* CONFIG_NFS_V4_2 */
-
static struct shrinker *acl_shrinker;
/*
@@ -163,9 +144,6 @@ int __init register_nfs_fs(void)
shrinker_register(acl_shrinker);
-#ifdef CONFIG_NFS_V4_2
- nfs_ssc_register_ops();
-#endif
return 0;
error_3:
nfs_unregister_sysctl();
@@ -185,9 +163,6 @@ void __exit unregister_nfs_fs(void)
shrinker_free(acl_shrinker);
nfs_unregister_sysctl();
unregister_nfs4_fs();
-#ifdef CONFIG_NFS_V4_2
- nfs_ssc_unregister_ops();
-#endif
unregister_filesystem(&nfs_fs_type);
}
diff --git a/fs/nfs_common/nfs_ssc.c b/fs/nfs_common/nfs_ssc.c
index 832246b22c51..e521e3c836fe 100644
--- a/fs/nfs_common/nfs_ssc.c
+++ b/fs/nfs_common/nfs_ssc.c
@@ -10,82 +10,112 @@
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/nfs_ssc.h>
-#include "../nfs/nfs4_fs.h"
+#include <linux/nfsd_ssc.h>
+struct nfs_ssc_client_ops_tbl {
+ const struct nfs4_ssc_client_ops __rcu *ssc_nfs4_ops;
+};
-struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl;
-EXPORT_SYMBOL_GPL(nfs_ssc_client_tbl);
+static struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl __read_mostly;
-#ifdef CONFIG_NFS_V4_2
/**
- * nfs42_ssc_register - install the NFS_V4 client ops in the nfs_ssc_client_tbl
- * @ops: NFS_V4 ops to be installed
+ * nfsd42_ssc_open - Open a file to be used for server-to-server copy
+ * @ss_mnt: active mount point on which the source file resides
+ * @src_fh: file handle of the source file to be copied
+ * @stateid: stateid to use for COPY operation
*
- * Return values:
- * None
+ * Caller must close the returned file using nfsd42_ssc_close().
+ *
+ * Return: an open file, or an ERR_PTR on error
*/
-void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops)
+struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
+ nfs4_stateid *stateid)
{
- nfs_ssc_client_tbl.ssc_nfs4_ops = ops;
+ /*
+ * Built under CONFIG_NFS_V4_2_SSC_HELPER, which the NFS client
+ * enables on its own. The dispatch below is live only when the
+ * server also sets CONFIG_NFSD_V4_2_INTER_SSC; without it the
+ * source file cannot be opened, so callers get -EIO.
+ */
+#if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC)
+ const struct nfs4_ssc_client_ops *ops;
+ struct file *res;
+
+ /*
+ * sco_open() sleeps and must not run inside an RCU read-side
+ * section. Pin the provider module so the open runs with the
+ * module held; try_module_get() fails once unregister begins,
+ * and the copy then gets -EIO.
+ */
+ rcu_read_lock();
+ ops = rcu_dereference(nfs_ssc_client_tbl.ssc_nfs4_ops);
+ if (ops && try_module_get(ops->owner)) {
+ rcu_read_unlock();
+ res = ops->sco_open(ss_mnt, src_fh, stateid);
+ module_put(ops->owner);
+ return res;
+ }
+ rcu_read_unlock();
+#endif
+
+ return ERR_PTR(-EIO);
}
-EXPORT_SYMBOL_GPL(nfs42_ssc_register);
+EXPORT_SYMBOL_GPL(nfsd42_ssc_open);
/**
- * nfs42_ssc_unregister - uninstall the NFS_V4 client ops from
- * the nfs_ssc_client_tbl
- * @ops: ops to be uninstalled
+ * nfsd42_ssc_close - Close a file opened with nfsd42_ssc_open()
+ * @filp: struct file to be closed
*
- * Return values:
- * None
+ * The real cleanup happens unconditionally in nfsd4_cleanup_inter_ssc().
+ * The client ops table is read under RCU; nfs42_ssc_unregister() calls
+ * synchronize_rcu() so unregistration cannot complete while a close is
+ * in flight.
*/
-void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops)
+void nfsd42_ssc_close(struct file *filp)
{
- if (nfs_ssc_client_tbl.ssc_nfs4_ops != ops)
- return;
+ /* Live only under CONFIG_NFSD_V4_2_INTER_SSC; see nfsd42_ssc_open(). */
+#if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC)
+ const struct nfs4_ssc_client_ops *ops;
- nfs_ssc_client_tbl.ssc_nfs4_ops = NULL;
+ rcu_read_lock();
+ ops = rcu_dereference(nfs_ssc_client_tbl.ssc_nfs4_ops);
+ if (ops)
+ ops->sco_close(filp);
+ rcu_read_unlock();
+#endif
}
-EXPORT_SYMBOL_GPL(nfs42_ssc_unregister);
-#endif /* CONFIG_NFS_V4_2 */
+EXPORT_SYMBOL_GPL(nfsd42_ssc_close);
#ifdef CONFIG_NFS_V4_2
/**
- * nfs_ssc_register - install the NFS_FS client ops in the nfs_ssc_client_tbl
- * @ops: NFS_FS ops to be installed
+ * nfs42_ssc_register - install the NFS_V4 client ops in the nfs_ssc_client_tbl
+ * @ops: NFS_V4 ops to be installed
*
* Return values:
* None
*/
-void nfs_ssc_register(const struct nfs_ssc_client_ops *ops)
+void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops)
{
- nfs_ssc_client_tbl.ssc_nfs_ops = ops;
+ rcu_assign_pointer(nfs_ssc_client_tbl.ssc_nfs4_ops, ops);
}
-EXPORT_SYMBOL_GPL(nfs_ssc_register);
+EXPORT_SYMBOL_GPL(nfs42_ssc_register);
/**
- * nfs_ssc_unregister - uninstall the NFS_FS client ops from
+ * nfs42_ssc_unregister - uninstall the NFS_V4 client ops from
* the nfs_ssc_client_tbl
* @ops: ops to be uninstalled
*
* Return values:
* None
*/
-void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops)
+void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops)
{
- if (nfs_ssc_client_tbl.ssc_nfs_ops != ops)
+ if (rcu_dereference_protected(nfs_ssc_client_tbl.ssc_nfs4_ops,
+ true) != ops)
return;
- nfs_ssc_client_tbl.ssc_nfs_ops = NULL;
-}
-EXPORT_SYMBOL_GPL(nfs_ssc_unregister);
-#else
-void nfs_ssc_register(const struct nfs_ssc_client_ops *ops)
-{
+ rcu_assign_pointer(nfs_ssc_client_tbl.ssc_nfs4_ops, NULL);
+ synchronize_rcu();
}
-EXPORT_SYMBOL_GPL(nfs_ssc_register);
-
-void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops)
-{
-}
-EXPORT_SYMBOL_GPL(nfs_ssc_unregister);
+EXPORT_SYMBOL_GPL(nfs42_ssc_unregister);
#endif /* CONFIG_NFS_V4_2 */
diff --git a/fs/nfsd/blocklayout.c b/fs/nfsd/blocklayout.c
index 5be7721c22c2..df02cf746479 100644
--- a/fs/nfsd/blocklayout.c
+++ b/fs/nfsd/blocklayout.c
@@ -9,6 +9,7 @@
#include <linux/nfsd/debug.h>
+#include "nfserr.h"
#include "blocklayoutxdr.h"
#include "pnfs.h"
#include "filecache.h"
diff --git a/fs/nfsd/blocklayoutxdr.c b/fs/nfsd/blocklayoutxdr.c
index f80dbc41fd5f..a6589f5c878a 100644
--- a/fs/nfsd/blocklayoutxdr.c
+++ b/fs/nfsd/blocklayoutxdr.c
@@ -8,11 +8,22 @@
#include <linux/nfs4.h>
#include "nfsd.h"
+#include "nfserr.h"
#include "blocklayoutxdr.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
+static __be32
+nfsd4_decode_deviceid4(struct xdr_stream *xdr, struct nfsd4_deviceid *devid)
+{
+ __be32 *p = xdr_inline_decode(xdr, NFS4_DEVICEID4_SIZE);
+
+ if (unlikely(!p))
+ return nfserr_bad_xdr;
+ svcxdr_decode_deviceid4(p, devid);
+ return nfs_ok;
+}
/**
* nfsd4_block_encode_layoutget - encode block/scsi layout extent array
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index b6e0c543e028..e5a0f1ababe6 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -21,6 +21,8 @@
#include <uapi/linux/nfsd_netlink.h>
#include "nfsd.h"
+#include "nfserr.h"
+#include "nfs4ctl.h"
#include "nfsfh.h"
#include "netns.h"
#include "pnfs.h"
@@ -1890,21 +1892,19 @@ __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
* check_nfsd_access - check if access to export is allowed.
* @exp: svc_export that is being accessed.
* @rqstp: svc_rqst attempting to access @exp.
- * @may_bypass_gss: reduce strictness of authorization check
*
* Return values:
* %nfs_ok if access is granted, or
* %nfserr_wrongsec if access is denied
*/
-__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
- bool may_bypass_gss)
+__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
{
__be32 status;
status = check_xprtsec_policy(exp, rqstp);
if (status != nfs_ok)
return status;
- return check_security_flavor(exp, rqstp, may_bypass_gss);
+ return check_security_flavor(exp, rqstp, false);
}
/*
diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
index d2b09cd76145..117fb28db1e0 100644
--- a/fs/nfsd/export.h
+++ b/fs/nfsd/export.h
@@ -104,8 +104,7 @@ int nfsexp_flags(struct svc_cred *cred, struct svc_export *exp);
__be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp);
__be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
bool may_bypass_gss);
-__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
- bool may_bypass_gss);
+__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp);
/*
* Function declarations
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index b9548eb17c77..3539149cc75f 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -43,6 +43,7 @@
#include "vfs.h"
#include "nfsd.h"
+#include "nfserr.h"
#include "nfsfh.h"
#include "netns.h"
#include "filecache.h"
diff --git a/fs/nfsd/flexfilelayout.c b/fs/nfsd/flexfilelayout.c
index 6d531285ab43..0deb913493a3 100644
--- a/fs/nfsd/flexfilelayout.c
+++ b/fs/nfsd/flexfilelayout.c
@@ -13,7 +13,9 @@
#include <linux/sunrpc/addr.h>
+#include "nfserr.h"
#include "flexfilelayoutxdr.h"
+#include "auth.h"
#include "pnfs.h"
#include "vfs.h"
@@ -23,10 +25,10 @@ static __be32
nfsd4_ff_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode,
const struct svc_fh *fhp, struct nfsd4_layoutget *args)
{
+ struct user_namespace *userns = nfsd_user_namespace(rqstp);
struct nfsd4_layout_seg *seg = &args->lg_seg;
u32 device_generation = 0;
int error;
- uid_t u;
struct pnfs_ff_layout *fl;
@@ -49,20 +51,22 @@ nfsd4_ff_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode,
fl->flags = FF_FLAGS_NO_LAYOUTCOMMIT | FF_FLAGS_NO_IO_THRU_MDS |
FF_FLAGS_NO_READ_IO;
- /* Do not allow a IOMODE_READ segment to have write pemissions */
- if (seg->iomode == IOMODE_READ) {
- u = from_kuid(&init_user_ns, inode->i_uid) + 1;
- fl->uid = make_kuid(&init_user_ns, u);
- } else
- fl->uid = inode->i_uid;
- fl->gid = inode->i_gid;
+ fl->uid = from_kuid_munged(userns, inode->i_uid);
+ fl->gid = from_kgid_munged(userns, inode->i_gid);
+
+ /*
+ * Do not allow an IOMODE_READ segment to have write permissions.
+ * The group is left intact so group-readable files stay readable;
+ * nfsd_setuser() squashes an unmapped uid to the export's anon ID.
+ */
+ if (seg->iomode == IOMODE_READ)
+ fl->uid++;
error = nfsd4_set_deviceid(&fl->deviceid, fhp, device_generation);
if (error)
goto out_error;
- fl->fh.size = fhp->fh_handle.fh_size;
- memcpy(fl->fh.data, &fhp->fh_handle.fh_raw, fl->fh.size);
+ fh_copy_shallow(&fl->fh, &fhp->fh_handle);
/* Give whole file layout segments */
seg->offset = 0;
diff --git a/fs/nfsd/flexfilelayoutxdr.c b/fs/nfsd/flexfilelayoutxdr.c
index 374e52d3064a..e297100a2ac3 100644
--- a/fs/nfsd/flexfilelayoutxdr.c
+++ b/fs/nfsd/flexfilelayoutxdr.c
@@ -6,6 +6,7 @@
#include <linux/nfs4.h>
#include "nfsd.h"
+#include "nfserr.h"
#include "flexfilelayoutxdr.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
@@ -30,10 +31,10 @@ nfsd4_ff_encode_layoutget(struct xdr_stream *xdr,
struct ff_idmap uid;
struct ff_idmap gid;
- fh_len = 4 + xdr_align_size(fl->fh.size);
+ fh_len = 4 + xdr_align_size(fl->fh.fh_size);
- uid.len = sprintf(uid.buf, "%u", from_kuid(&init_user_ns, fl->uid));
- gid.len = sprintf(gid.buf, "%u", from_kgid(&init_user_ns, fl->gid));
+ uid.len = sprintf(uid.buf, "%u", fl->uid);
+ gid.len = sprintf(gid.buf, "%u", fl->gid);
/* data server entry: deviceid + efficiency + stateid + fh list +
* user + group + flags + stats_collect_hint
@@ -68,7 +69,7 @@ nfsd4_ff_encode_layoutget(struct xdr_stream *xdr,
sizeof(stateid_opaque_t));
*p++ = cpu_to_be32(1); /* single file handle */
- p = xdr_encode_opaque(p, fl->fh.data, fl->fh.size);
+ p = xdr_encode_opaque(p, fl->fh.fh_raw, fl->fh.fh_size);
p = xdr_encode_opaque(p, uid.buf, uid.len);
p = xdr_encode_opaque(p, gid.buf, gid.len);
diff --git a/fs/nfsd/flexfilelayoutxdr.h b/fs/nfsd/flexfilelayoutxdr.h
index 6d5a1066a903..f7d1dd0708ec 100644
--- a/fs/nfsd/flexfilelayoutxdr.h
+++ b/fs/nfsd/flexfilelayoutxdr.h
@@ -6,6 +6,7 @@
#define _NFSD_FLEXFILELAYOUTXDR_H 1
#include <linux/inet.h>
+#include "nfsfh.h"
#include "xdr4.h"
#define FF_FLAGS_NO_LAYOUTCOMMIT 1
@@ -35,11 +36,12 @@ struct pnfs_ff_device_addr {
struct pnfs_ff_layout {
u32 flags;
u32 stats_collect_hint;
- kuid_t uid;
- kgid_t gid;
+ /* Values to encode; nfsd4_ff_proc_layoutget() has mapped these */
+ u32 uid;
+ u32 gid;
struct nfsd4_deviceid deviceid;
stateid_t stateid;
- struct nfs_fh fh;
+ struct knfsd_fh fh;
};
__be32 nfsd4_ff_encode_getdeviceinfo(struct xdr_stream *xdr,
diff --git a/fs/nfsd/localio.c b/fs/nfsd/localio.c
index c458c01e9478..33b56d1b3f44 100644
--- a/fs/nfsd/localio.c
+++ b/fs/nfsd/localio.c
@@ -11,11 +11,9 @@
#include <linux/exportfs.h>
#include <linux/sunrpc/svcauth.h>
#include <linux/sunrpc/clnt.h>
-#include <linux/nfs.h>
#include <linux/nfs_common.h>
+#include <linux/nfs_fh.h>
#include <linux/nfslocalio.h>
-#include <linux/nfs_fs.h>
-#include <linux/nfs_xdr.h>
#include <linux/string.h>
#include "nfsd.h"
@@ -55,7 +53,7 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
struct nfsd_file *localio;
__be32 beres;
- if (nfs_fh->size > NFS4_FHSIZE)
+ if (nfs_fh->size > NFS_MAXFHSIZE)
return ERR_PTR(-EINVAL);
if (!nfsd_net_try_get(net))
@@ -68,7 +66,7 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
return localio;
/* nfs_fh -> svc_fh */
- fh_init(&fh, NFS4_FHSIZE);
+ fh_init(&fh, NFSD_FHSIZE_UNSPEC);
fh.fh_handle.fh_size = nfs_fh->size;
memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size);
@@ -179,7 +177,7 @@ static bool localio_decode_uuidarg(struct svc_rqst *rqstp,
struct localio_uuidarg *argp = rqstp->rq_argp;
u8 uuid[UUID_SIZE];
- if (decode_opaque_fixed(xdr, uuid, UUID_SIZE))
+ if (xdr_stream_decode_opaque_fixed(xdr, uuid, UUID_SIZE) < 0)
return false;
import_uuid(&argp->uuid, uuid);
diff --git a/fs/nfsd/lockd.c b/fs/nfsd/lockd.c
index 72a5b499839d..5ec0f5456063 100644
--- a/fs/nfsd/lockd.c
+++ b/fs/nfsd/lockd.c
@@ -10,6 +10,7 @@
#include <linux/file.h>
#include <linux/lockd/bind.h>
#include "nfsd.h"
+#include "nfserr.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_LOCKD
@@ -33,8 +34,7 @@ static int nlm_fopen(struct svc_rqst *rqstp, struct nfs_fh *f,
int access;
struct svc_fh fh;
- /* must initialize before using! but maxsize doesn't matter */
- fh_init(&fh,0);
+ fh_init(&fh, NFSD_FHSIZE_UNSPEC);
fh.fh_handle.fh_size = f->size;
memcpy(&fh.fh_handle.fh_raw, f->data, f->size);
fh.fh_export = NULL;
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 190f5a001900..33610deda3b0 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -6,6 +6,7 @@
*/
#include "nfsd.h"
+#include "nfserr.h"
/* FIXME: nfsacl.h is a broken header */
#include <linux/nfsacl.h>
#include <linux/gfp.h>
@@ -16,6 +17,48 @@
#define NFSDDBG_FACILITY NFSDDBG_PROC
/*
+ * These maps are identical to the NFSv3 maps (nfs3proc.c). This enables
+ * the behavior of the two versions to diverge if needed.
+ */
+static const struct nfsd_access_map nfsd2_regaccess[] = {
+ { NFS3_ACCESS_READ, NFSD_MAY_READ },
+ { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC },
+ { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+ { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_map nfsd2_diraccess[] = {
+ { NFS3_ACCESS_READ, NFSD_MAY_READ },
+ { NFS3_ACCESS_LOOKUP, NFSD_MAY_EXEC },
+ { NFS3_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+ { NFS3_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE },
+ { NFS3_ACCESS_DELETE, NFSD_MAY_REMOVE },
+ { 0, 0 }
+};
+
+/*
+ * Some clients - Solaris 2.6 at least, make an access call to the NFS
+ * server to check for access for things like /dev/null (which really,
+ * NFSD doesn't care about). So NFSD provides simple access checking
+ * for those objects, looking mainly at mode bits, ignoring read-only
+ * filesystem checks.
+ */
+static const struct nfsd_access_map nfsd2_otheraccess[] = {
+ { NFS3_ACCESS_READ, NFSD_MAY_READ },
+ { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC },
+ { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
+ { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_maps nfsd2_access_maps = {
+ .regular = nfsd2_regaccess,
+ .directory = nfsd2_diraccess,
+ .other = nfsd2_otheraccess,
+};
+
+/*
* NULL call.
*/
static __be32
@@ -180,7 +223,9 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
fh_copy(&resp->fh, &argp->fh);
resp->access = argp->access;
- resp->status = nfsd_access(rqstp, &resp->fh, &resp->access, NULL);
+
+ resp->status = nfsd_access(rqstp, &resp->fh, &nfsd2_access_maps,
+ &resp->access, NULL);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 6b6b289db636..7183995182ab 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -6,6 +6,7 @@
*/
#include "nfsd.h"
+#include "nfserr.h"
/* FIXME: nfsacl.h is a broken header */
#include <linux/nfsacl.h>
#include <linux/gfp.h>
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 0904d953d10e..17bbe5d13f18 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -13,6 +13,7 @@
#include "cache.h"
#include "xdr3.h"
#include "vfs.h"
+#include "nfserr.h"
#include "filecache.h"
#include "trace.h"
@@ -48,6 +49,58 @@ static bool nfsd3_time_in_range(const struct iattr *iap)
return true;
}
+static const struct nfsd_access_map nfsd3_regaccess[] = {
+ { NFS3_ACCESS_READ, NFSD_MAY_READ },
+ { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC },
+ { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+ { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_map nfsd3_diraccess[] = {
+ { NFS3_ACCESS_READ, NFSD_MAY_READ },
+ { NFS3_ACCESS_LOOKUP, NFSD_MAY_EXEC },
+ { NFS3_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+ { NFS3_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE },
+ { NFS3_ACCESS_DELETE, NFSD_MAY_REMOVE },
+ { 0, 0 }
+};
+
+/*
+ * Some clients - Solaris 2.6 at least, make an access call to the NFS
+ * server to check for access for things like /dev/null (which really,
+ * NFSD doesn't care about). So NFSD provides simple access checking
+ * for those objects, looking mainly at mode bits, ignoring read-only
+ * filesystem checks.
+ */
+static const struct nfsd_access_map nfsd3_otheraccess[] = {
+ { NFS3_ACCESS_READ, NFSD_MAY_READ },
+ { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC },
+ { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
+ { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_maps nfsd3_access_maps = {
+ .regular = nfsd3_regaccess,
+ .directory = nfsd3_diraccess,
+ .other = nfsd3_otheraccess,
+};
+
+static int nfsd3_iocb_flags(enum nfs3_stable_how how)
+{
+ switch (how) {
+ case NFS_FILE_SYNC:
+ /* persist data and timestamps */
+ return IOCB_DSYNC | IOCB_SYNC;
+ case NFS_DATA_SYNC:
+ /* persist data only */
+ return IOCB_DSYNC;
+ default:
+ return 0;
+ }
+}
+
static __be32 nfsd3_map_status(__be32 status)
{
switch (status) {
@@ -171,7 +224,8 @@ nfsd3_proc_access(struct svc_rqst *rqstp)
fh_copy(&resp->fh, &argp->fh);
resp->access = argp->access;
- resp->status = nfsd_access(rqstp, &resp->fh, &resp->access, NULL);
+ resp->status = nfsd_access(rqstp, &resp->fh, &nfsd3_access_maps,
+ &resp->access, NULL);
resp->status = nfsd3_map_status(resp->status);
return rpc_success;
}
@@ -260,7 +314,8 @@ nfsd3_proc_write(struct svc_rqst *rqstp)
resp->committed = argp->stable;
resp->status = nfsd_write(rqstp, &resp->fh, argp->offset,
&argp->payload, &cnt,
- resp->committed, resp->verf);
+ nfsd3_iocb_flags(resp->committed),
+ resp->verf);
resp->count = cnt;
resp->status = nfsd3_map_status(resp->status);
return rpc_success;
@@ -282,6 +337,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_attrs attrs = {
.na_iattr = iap,
};
+ struct svc_export *exp;
__u32 v_mtime, v_atime;
struct inode *inode;
__be32 status;
@@ -320,7 +376,23 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
goto out;
}
- status = fh_compose(resfhp, fhp->fh_export, child, fhp);
+ exp = exp_get(fhp->fh_export);
+ if (argp->createmode == NFS3_CREATE_UNCHECKED) {
+ /*
+ * If name is already in dcache we need to check for mountpoints
+ */
+ if (d_is_reg(child) &&
+ unlikely(nfsd_mountpoint(child, exp))) {
+ status = nfsd_cross_mnt(rqstp, &child, &exp);
+ if (status != nfs_ok) {
+ exp_put(exp);
+ goto out;
+ }
+ }
+ }
+
+ status = fh_compose(resfhp, exp, child, fhp);
+ exp_put(exp);
if (status != nfs_ok)
goto out;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index e481804bb120..090cea8e545d 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -13,6 +13,7 @@
#include "auth.h"
#include "netns.h"
#include "vfs.h"
+#include "nfserr.h"
/*
* Force construction of an empty post-op attr
@@ -556,6 +557,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return false;
if (xdr_stream_decode_u32(xdr, &args->stable) < 0)
return false;
+ if (args->stable > NFS_FILE_SYNC)
+ return false;
/* opaque data */
if (xdr_stream_decode_u32(xdr, &args->len) < 0)
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index 2c2f2fd89e87..94f6ad381ebe 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -40,6 +40,7 @@
#include "nfsfh.h"
#include "nfsd.h"
+#include "nfserr.h"
#include "acl.h"
#include "vfs.h"
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 19dc337502ca..a6b31d3f2bf6 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -37,6 +37,7 @@
#include <linux/sunrpc/svc_xprt.h>
#include <linux/slab.h>
#include "nfsd.h"
+#include "nfserr.h"
#include "state.h"
#include "netns.h"
#include "stats.h"
@@ -1529,12 +1530,14 @@ out:
/**
* nfsd41_cb_destroy_referring_call_list - release referring call info
- * @cb: context of a callback that has completed
+ * @cb: context of callback to release referring calls from
*
* Callers who allocate referring calls using nfsd41_cb_referring_call() must
* release those resources by calling nfsd41_cb_destroy_referring_call_list.
*
- * Caller serializes access to @cb.
+ * Caller serializes access to @cb. No CB_COMPOUND for @cb may be in
+ * flight, because encode_cb_sequence4args() walks this list as it
+ * encodes.
*/
void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb)
{
@@ -1556,6 +1559,7 @@ void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb)
list_del(&rcl->__list);
kfree(rcl);
}
+ cb->cb_nr_referring_call_list = 0;
}
static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)
diff --git a/fs/nfsd/nfs4ctl.h b/fs/nfsd/nfs4ctl.h
new file mode 100644
index 000000000000..bcec4c4ef1d5
--- /dev/null
+++ b/fs/nfsd/nfs4ctl.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Entry points by which the knfsd core drives the optional NFSv4
+ * subsystem: state lifecycle, the laundromat workqueue, the recovery
+ * directory, junctions, the CLD notifier, and leases-net setup.
+ *
+ * Separated from nfsd.h so that the many translation units that
+ * include nfsd.h but call none of these -- among them the NFSv2 and
+ * NFSv3 paths -- do not have to parse them. The CONFIG_NFSD_V4=n
+ * stubs let the version-agnostic callers invoke the routines
+ * unconditionally.
+ */
+
+#ifndef LINUX_NFSD_NFS4CTL_H
+#define LINUX_NFSD_NFS4CTL_H
+
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+struct net;
+struct inode;
+struct dentry;
+struct svc_rqst;
+struct nfsd_net;
+
+#ifdef CONFIG_NFSD_V4
+extern unsigned long max_delegations;
+int nfsd4_init_slabs(void);
+void nfsd4_free_slabs(void);
+int nfs4_state_start(void);
+int nfs4_state_start_net(struct net *net);
+void nfs4_state_shutdown(void);
+void nfs4_state_shutdown_net(struct net *net);
+int nfs4_reset_recoverydir(char *recdir);
+char * nfs4_recoverydir(void);
+bool nfsd4_spo_must_allow(struct svc_rqst *rqstp);
+int nfsd4_create_laundry_wq(void);
+void nfsd4_destroy_laundry_wq(void);
+bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode);
+
+extern int nfsd4_is_junction(struct dentry *dentry);
+extern int register_cld_notifier(void);
+extern void unregister_cld_notifier(void);
+#ifdef CONFIG_NFSD_V4_2_INTER_SSC
+extern void nfsd4_ssc_init_umount_work(struct nfsd_net *nn);
+#endif
+
+extern void nfsd4_init_leases_net(struct nfsd_net *nn);
+
+#else /* CONFIG_NFSD_V4 */
+static inline int nfsd4_init_slabs(void) { return 0; }
+static inline void nfsd4_free_slabs(void) { }
+static inline int nfs4_state_start(void) { return 0; }
+static inline int nfs4_state_start_net(struct net *net) { return 0; }
+static inline void nfs4_state_shutdown(void) { }
+static inline void nfs4_state_shutdown_net(struct net *net) { }
+static inline int nfs4_reset_recoverydir(char *recdir) { return 0; }
+static inline char * nfs4_recoverydir(void) {return NULL; }
+static inline bool nfsd4_spo_must_allow(struct svc_rqst *rqstp)
+{
+ return false;
+}
+static inline int nfsd4_create_laundry_wq(void) { return 0; };
+static inline void nfsd4_destroy_laundry_wq(void) {};
+static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp,
+ struct inode *inode)
+{
+ return false;
+}
+
+static inline int nfsd4_is_junction(struct dentry *dentry)
+{
+ return 0;
+}
+
+static inline void nfsd4_init_leases_net(struct nfsd_net *nn) { };
+
+#define register_cld_notifier() 0
+#define unregister_cld_notifier() do { } while(0)
+
+#endif /* CONFIG_NFSD_V4 */
+
+#endif /* LINUX_NFSD_NFS4CTL_H */
diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c
index e9faf8b78f74..4e5297593963 100644
--- a/fs/nfsd/nfs4idmap.c
+++ b/fs/nfsd/nfs4idmap.c
@@ -41,6 +41,7 @@
#include "auth.h"
#include "idmap.h"
#include "nfsd.h"
+#include "nfserr.h"
#include "netns.h"
#include "vfs.h"
diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c
index 22bcb6d09f70..4187202f9acc 100644
--- a/fs/nfsd/nfs4layouts.c
+++ b/fs/nfsd/nfs4layouts.c
@@ -9,6 +9,7 @@
#include <linux/sched.h>
#include <linux/sunrpc/addr.h>
+#include "nfserr.h"
#include "pnfs.h"
#include "netns.h"
#include "trace.h"
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 50c07561e31f..88385a161b4d 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -38,19 +38,22 @@
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/namei.h>
+#include <linux/pagemap.h>
#include <linux/sunrpc/addr.h>
-#include <linux/nfs_ssc.h>
+#include <linux/nfsd_ssc.h>
#include "attr4.h"
#include "idmap.h"
#include "cache.h"
#include "xdr4.h"
+#include "nfs4ctl.h"
#include "vfs.h"
#include "current_stateid.h"
#include "netns.h"
#include "acl.h"
#include "pnfs.h"
+#include "nfserr.h"
#include "trace.h"
static bool inter_copy_offload_enable;
@@ -69,6 +72,57 @@ MODULE_PARM_DESC(nfsd4_ssc_umount_timeout,
#define NFSDDBG_FACILITY NFSDDBG_PROC
+static const struct nfsd_access_map nfsd4_regaccess[] = {
+ { NFS4_ACCESS_READ, NFSD_MAY_READ },
+ { NFS4_ACCESS_EXECUTE, NFSD_MAY_EXEC },
+ { NFS4_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+ { NFS4_ACCESS_EXTEND, NFSD_MAY_WRITE },
+ { NFS4_ACCESS_XAREAD, NFSD_MAY_READ },
+ { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE },
+ { NFS4_ACCESS_XALIST, NFSD_MAY_READ },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_map nfsd4_diraccess[] = {
+ { NFS4_ACCESS_READ, NFSD_MAY_READ },
+ { NFS4_ACCESS_LOOKUP, NFSD_MAY_EXEC },
+ { NFS4_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+ { NFS4_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE },
+ { NFS4_ACCESS_DELETE, NFSD_MAY_REMOVE },
+ { NFS4_ACCESS_XAREAD, NFSD_MAY_READ },
+ { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE },
+ { NFS4_ACCESS_XALIST, NFSD_MAY_READ },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_map nfsd4_otheraccess[] = {
+ { NFS4_ACCESS_READ, NFSD_MAY_READ },
+ { NFS4_ACCESS_EXECUTE, NFSD_MAY_EXEC },
+ { NFS4_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
+ { NFS4_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
+ { 0, 0 }
+};
+
+static const struct nfsd_access_maps nfsd4_access_maps = {
+ .regular = nfsd4_regaccess,
+ .directory = nfsd4_diraccess,
+ .other = nfsd4_otheraccess,
+};
+
+static int nfsd4_iocb_flags(enum stable_how4 how)
+{
+ switch (how) {
+ case FILE_SYNC4:
+ /* persist data and timestamps */
+ return IOCB_DSYNC | IOCB_SYNC;
+ case DATA_SYNC4:
+ /* persist data only */
+ return IOCB_DSYNC;
+ default:
+ return 0;
+ }
+}
+
static u32 nfsd_attrmask[] = {
NFSD_WRITEABLE_ATTRS_WORD0,
NFSD_WRITEABLE_ATTRS_WORD1,
@@ -169,23 +223,17 @@ do_open_permission(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfs
return fh_verify(rqstp, current_fh, S_IFREG, accmode);
}
-static __be32 nfsd_check_obj_isreg(struct svc_fh *fh, u32 minor_version)
+static int nfsd_check_obj_isreg(struct dentry *child)
{
- umode_t mode = d_inode(fh->fh_dentry)->i_mode;
+ umode_t mode = d_inode(child)->i_mode;
if (S_ISREG(mode))
- return nfs_ok;
+ return 0;
if (S_ISDIR(mode))
- return nfserr_isdir;
+ return -EISDIR;
if (S_ISLNK(mode))
- return nfserr_symlink;
-
- /* RFC 7530 - 16.16.6 */
- if (minor_version == 0)
- return nfserr_symlink;
- else
- return nfserr_wrong_type;
-
+ return -ELOOP;
+ return -EFTYPE;
}
static void nfsd4_set_open_owner_reply_cache(struct nfsd4_compound_state *cstate, struct nfsd4_open *open, struct svc_fh *resfh)
@@ -202,40 +250,50 @@ static inline bool nfsd4_create_is_exclusive(int createmode)
createmode == NFS4_CREATE_EXCLUSIVE4_1;
}
-static __be32
-nfsd4_vfs_create(struct svc_fh *fhp, struct dentry **child,
- struct nfsd4_open *open)
+static struct file *do_lookup_open(struct path *parent,
+ struct qstr *name,
+ unsigned int oflags,
+ umode_t mode)
{
- struct file *filp;
+ struct file *filp = NULL;
struct path path;
- int oflags;
+ struct dentry *child;
+ int want_write_err = 0;
- oflags = O_CREAT | O_LARGEFILE;
- if (nfsd4_create_is_exclusive(open->op_createmode))
- oflags |= O_EXCL;
+ want_write_err = mnt_want_write(parent->mnt);
- switch (open->op_share_access & NFS4_SHARE_ACCESS_BOTH) {
- case NFS4_SHARE_ACCESS_WRITE:
- oflags |= O_WRONLY;
- break;
- case NFS4_SHARE_ACCESS_BOTH:
- oflags |= O_RDWR;
- break;
- default:
- oflags |= O_RDONLY;
+ child = start_creating(&nop_mnt_idmap, parent->dentry, name);
+ if (IS_ERR(child)) {
+ filp = ERR_CAST(child);
+ goto out;
}
+ path.mnt = parent->mnt;
+ path.dentry = child;
- path.mnt = fhp->fh_export->ex_path.mnt;
- path.dentry = *child;
- filp = dentry_create(&path, oflags, open->op_iattr.ia_mode,
- current_cred());
- *child = path.dentry;
-
- if (IS_ERR(filp))
- return nfserrno(PTR_ERR(filp));
+ if (d_really_is_positive(child)) {
+ /*
+ * open the file so that we consistently have a valid
+ * op_filp and consequently a valid ->f_path.dentry.
+ */
+ int err = nfsd_check_obj_isreg(child);
- open->op_filp = filp;
- return nfs_ok;
+ if (err)
+ filp = ERR_PTR(err);
+ else
+ filp = dentry_open(&path, oflags, current_cred());
+ } else if (!(oflags & O_CREAT)) {
+ filp = ERR_PTR(-ENOENT);
+ } else if (want_write_err) {
+ filp = ERR_PTR(want_write_err);
+ } else {
+ filp = dentry_create(&path, oflags, mode, current_cred());
+ child = path.dentry;
+ }
+ end_creating(child);
+out:
+ if (!want_write_err)
+ mnt_drop_write(parent->mnt);
+ return filp;
}
/*
@@ -254,11 +312,15 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
.na_iattr = iap,
.na_seclabel = &open->op_label,
};
- struct dentry *parent, *child = ERR_PTR(-EINVAL);
+ int oflags = O_CREAT | O_LARGEFILE;
+ struct dentry *child = ERR_PTR(-EINVAL);
+ struct path parent = {
+ .mnt = fhp->fh_export->ex_path.mnt,
+ .dentry = fhp->fh_dentry,
+ };
__u32 v_mtime, v_atime;
- struct inode *inode;
- __be32 status;
- int host_err;
+ __be32 status, create_status;
+ int want_write_err;
if (name_is_dot_dotdot(open->op_fname, open->op_fnamelen))
return nfserr_exist;
@@ -268,25 +330,67 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
if (status != nfs_ok)
return status;
- parent = fhp->fh_dentry;
- inode = d_inode(parent);
- host_err = fh_want_write(fhp);
- if (host_err)
- return nfserrno(host_err);
+ if (open->op_createmode == NFS4_CREATE_UNCHECKED) {
+ /*
+ * If name is already in dcache we need to check for mountpoints
+ */
+ child = try_lookup_noperm(&QSTR_LEN(open->op_fname,
+ open->op_fnamelen),
+ parent.dentry);
+ if (child && !IS_ERR(child) && d_is_reg(child) &&
+ unlikely(nfsd_mountpoint(child, fhp->fh_export))) {
+ struct svc_export *exp = exp_get(fhp->fh_export);
+
+ status = nfsd_cross_mnt(rqstp, &child, &exp);
+ if (status == nfs_ok)
+ status = fh_compose(resfhp, exp,
+ child, fhp);
+ fh_fill_post_noop(fhp);
+ open->op_truncate =
+ (iap->ia_valid & ATTR_SIZE) &&
+ !iap->ia_size;
+ dput(child);
+ exp_put(exp);
+ return status;
+ }
+ if (!IS_ERR(child))
+ dput(child);
+ }
- if (open->op_acl) {
+ if (!IS_POSIXACL(d_inode(parent.dentry)))
+ iap->ia_mode &= ~current_umask();
+
+ /*
+ * For the EXCLUSIVE modes we do our own uniqueness tests
+ * so don't want O_EXCL.
+ */
+ if (open->op_createmode == NFS4_CREATE_GUARDED)
+ oflags |= O_EXCL;
+
+ switch (open->op_share_access & NFS4_SHARE_ACCESS_BOTH) {
+ case NFS4_SHARE_ACCESS_WRITE:
+ oflags |= O_WRONLY;
+ break;
+ case NFS4_SHARE_ACCESS_BOTH:
+ oflags |= O_RDWR;
+ break;
+ default:
+ oflags |= O_RDONLY;
+ }
+
+ if (!is_create_with_attrs(open)) {
+ /* No attrs to check */
+ } else if (open->op_acl) {
if (open->op_dpacl || open->op_pacl) {
- status = nfserr_inval;
- goto out;
+ /* Cannot specify both NFSv4 and Posix ACLs */
+ return nfserr_inval;
}
- if (is_create_with_attrs(open)) {
- status = nfsd4_acl_to_attr(NF4REG, open->op_acl,
+ status = nfsd4_acl_to_attr(NF4REG, open->op_acl,
&attrs);
- if (status)
- goto out;
- }
- } else if (is_create_with_attrs(open)) {
+ if (status)
+ return status;
+ } else {
/* The dpacl and pacl will get released by nfsd_attrs_free(). */
attrs.na_dpacl = open->op_dpacl;
attrs.na_pacl = open->op_pacl;
@@ -294,19 +398,6 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
open->op_pacl = NULL;
}
- child = start_creating(&nop_mnt_idmap, parent,
- &QSTR_LEN(open->op_fname, open->op_fnamelen));
- if (IS_ERR(child)) {
- status = nfserrno(PTR_ERR(child));
- goto out;
- }
-
- if (d_really_is_negative(child)) {
- status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
- if (status != nfs_ok)
- goto out;
- }
-
v_mtime = 0;
v_atime = 0;
if (nfsd4_create_is_exclusive(open->op_createmode)) {
@@ -322,24 +413,53 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
*/
v_mtime = verifier[0] & 0x7fffffff;
v_atime = verifier[1] & 0x7fffffff;
+
+ iap->ia_valid |= ATTR_MTIME | ATTR_ATIME |
+ ATTR_MTIME_SET|ATTR_ATIME_SET;
+ iap->ia_mtime.tv_sec = v_mtime;
+ iap->ia_atime.tv_sec = v_atime;
+ iap->ia_mtime.tv_nsec = 0;
+ iap->ia_atime.tv_nsec = 0;
}
- if (d_really_is_positive(child)) {
- /* NFSv4 protocol requires change attributes even though
- * no change happened.
- */
- status = fh_fill_both_attrs(fhp);
- if (status != nfs_ok)
- goto out;
+ create_status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+ if (create_status)
+ /* Might still succeed if no create is needed */
+ oflags &= ~O_CREAT;
+
+ open->op_filp = do_lookup_open(&parent,
+ &QSTR_LEN(open->op_fname,
+ open->op_fnamelen),
+ oflags,
+ open->op_iattr.ia_mode);
+ if (IS_ERR(open->op_filp)) {
+ status = nfserrno(PTR_ERR(open->op_filp));
+ open->op_filp = NULL;
+ if (status == nfserr_noent && create_status)
+ status = create_status;
+ goto out;
+ }
- status = fh_compose(resfhp, fhp->fh_export, child, fhp);
- if (status != nfs_ok)
- goto out;
+ child = open->op_filp->f_path.dentry;
+ open->op_created = open->op_filp->f_mode & FMODE_CREATED;
- switch (open->op_createmode) {
- case NFS4_CREATE_UNCHECKED:
- if (!d_is_reg(child))
- break;
+ status = fh_compose(resfhp, fhp->fh_export, child, fhp);
+ if (status != nfs_ok)
+ goto out;
+
+ if (!open->op_created &&
+ nfsd4_create_is_exclusive(open->op_createmode) &&
+ inode_get_mtime_sec(d_inode(child)) == v_mtime &&
+ inode_get_atime_sec(d_inode(child)) == v_atime &&
+ d_inode(child)->i_size == 0)
+ open->op_created = true;
+
+ if (!open->op_created) {
+ if (open->op_createmode == NFS4_CREATE_UNCHECKED) {
+ /* NFSv4 protocol requires change attributes
+ * even though no change happened.
+ */
+ fh_fill_post_noop(fhp);
/*
* In NFSv4, we don't want to truncate the file
@@ -347,63 +467,30 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
* some other reason. Furthermore, if the size is
* nonzero, we should ignore it according to spec!
*/
- open->op_truncate = (iap->ia_valid & ATTR_SIZE) &&
- !iap->ia_size;
- break;
- case NFS4_CREATE_GUARDED:
+ open->op_truncate = (d_is_reg(child) &&
+ (iap->ia_valid & ATTR_SIZE) &&
+ !iap->ia_size);
+ } else
status = nfserr_exist;
- break;
- case NFS4_CREATE_EXCLUSIVE:
- if (inode_get_mtime_sec(d_inode(child)) == v_mtime &&
- inode_get_atime_sec(d_inode(child)) == v_atime &&
- d_inode(child)->i_size == 0) {
- open->op_created = true;
- break; /* subtle */
- }
- status = nfserr_exist;
- break;
- case NFS4_CREATE_EXCLUSIVE4_1:
- if (inode_get_mtime_sec(d_inode(child)) == v_mtime &&
- inode_get_atime_sec(d_inode(child)) == v_atime &&
- d_inode(child)->i_size == 0) {
- open->op_created = true;
- goto set_attr; /* subtle */
- }
- status = nfserr_exist;
- }
goto out;
}
-
- if (!IS_POSIXACL(inode))
- iap->ia_mode &= ~current_umask();
-
- status = fh_fill_pre_attrs(fhp);
- if (status != nfs_ok)
- goto out;
- status = nfsd4_vfs_create(fhp, &child, open);
- if (status != nfs_ok)
- goto out;
- open->op_created = true;
+ /* file was created */
fh_fill_post_attrs(fhp);
- status = fh_compose(resfhp, fhp->fh_export, child, fhp);
- if (status != nfs_ok)
- goto out;
-
/* A newly created file already has a file size of zero. */
if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0))
iap->ia_valid &= ~ATTR_SIZE;
- if (nfsd4_create_is_exclusive(open->op_createmode)) {
- iap->ia_valid = ATTR_MTIME | ATTR_ATIME |
- ATTR_MTIME_SET|ATTR_ATIME_SET;
- iap->ia_mtime.tv_sec = v_mtime;
- iap->ia_atime.tv_sec = v_atime;
- iap->ia_mtime.tv_nsec = 0;
- iap->ia_atime.tv_nsec = 0;
- }
-set_attr:
- status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
+ /* We will need write access to set the attrs */
+ want_write_err = fh_want_write(fhp);
+ if (!want_write_err) {
+ status = nfsd_create_setattr(rqstp, fhp,
+ resfhp, &attrs);
+ fh_drop_write(fhp);
+ } else if (nfsd_attrs_valid(&attrs)) {
+ /* Needed write access */
+ status = nfserrno(want_write_err);
+ }
if (attrs.na_labelerr)
open->op_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
@@ -414,9 +501,7 @@ set_attr:
if (attrs.na_paclerr)
open->op_bmval[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL;
out:
- end_creating(child);
nfsd_attrs_free(&attrs);
- fh_drop_write(fhp);
return status;
}
@@ -465,6 +550,9 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
fh_init(*resfh, NFS4_FHSIZE);
open->op_truncate = false;
+ status = fh_fill_pre_attrs_unlocked(current_fh);
+ if (status)
+ goto out;
if (open->op_create) {
/* FIXME: check session persistence and pnfs flags.
* The nfsv4.1 spec requires the following semantics:
@@ -496,15 +584,15 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
} else {
status = nfsd_lookup(rqstp, current_fh,
open->op_fname, open->op_fnamelen, *resfh);
- if (status == nfs_ok)
- /* NFSv4 protocol requires change attributes even though
- * no change happened.
- */
- status = fh_fill_both_attrs(current_fh);
+ /*
+ * NFSv4 protocol requires change attributes even though
+ * no change happened.
+ */
+ fh_fill_post_noop(current_fh);
}
if (status)
goto out;
- status = nfsd_check_obj_isreg(*resfh, cstate->minorversion);
+ status = nfserrno(nfsd_check_obj_isreg((*resfh)->fh_dentry));
if (status)
goto out;
@@ -516,6 +604,10 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
status = do_open_permission(rqstp, *resfh, open, accmode);
set_change_info(&open->op_cinfo, current_fh);
out:
+ if (status == nfserr_wrong_type && cstate->minorversion == 0)
+ /* RFC 7530 - 16.16.6 */
+ return nfserr_symlink;
+
return status;
}
@@ -788,17 +880,18 @@ nfsd4_access(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_access *access = &u->access;
u32 access_full;
- access_full = NFS3_ACCESS_FULL;
+ access_full = NFS4_ACCESS_READ | NFS4_ACCESS_LOOKUP |
+ NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND |
+ NFS4_ACCESS_DELETE | NFS4_ACCESS_EXECUTE;
if (cstate->minorversion >= 2)
access_full |= NFS4_ACCESS_XALIST | NFS4_ACCESS_XAREAD |
NFS4_ACCESS_XAWRITE;
if (access->ac_req_access & ~access_full)
return nfserr_inval;
-
access->ac_resp_access = access->ac_req_access;
- return nfsd_access(rqstp, &cstate->current_fh, &access->ac_resp_access,
- &access->ac_supported);
+ return nfsd_access(rqstp, &cstate->current_fh, &nfsd4_access_maps,
+ &access->ac_resp_access, &access->ac_supported);
}
static __be32
@@ -1377,7 +1470,7 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
write->wr_how_written = write->wr_stable_how;
status = nfsd_vfs_write(rqstp, &cstate->current_fh, nf,
write->wr_offset, &write->wr_payload,
- &cnt, write->wr_how_written,
+ &cnt, nfsd4_iocb_flags(write->wr_how_written),
(__be32 *)write->wr_verifier.data);
nfsd_file_put(nf);
@@ -1431,16 +1524,37 @@ nfsd4_clone(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
{
struct nfsd4_clone *clone = &u->clone;
struct nfsd_file *src, *dst;
+ bool sync_failed = false;
+ errseq_t since;
__be32 status;
+ int host_err;
status = nfsd4_verify_copy(rqstp, cstate, &clone->cl_src_stateid, &src,
&clone->cl_dst_stateid, &dst);
if (status)
goto out;
- status = nfsd4_clone_file_range(rqstp, src, clone->cl_src_pos,
- dst, clone->cl_dst_pos, clone->cl_count,
- EX_ISSYNC(cstate->current_fh.fh_export));
+ host_err = nfsd_clone_file_range(src->nf_file, clone->cl_src_pos,
+ dst->nf_file, clone->cl_dst_pos,
+ clone->cl_count, &since);
+ if (!host_err && EX_ISSYNC(cstate->current_fh.fh_export)) {
+ host_err = nfsd_clone_sync_range(src->nf_file, dst->nf_file,
+ clone->cl_dst_pos,
+ clone->cl_count, since);
+ sync_failed = host_err < 0;
+ }
+ if (host_err < 0) {
+ trace_nfsd_clone_file_range_err(rqstp, &cstate->save_fh,
+ clone->cl_src_pos, &cstate->current_fh,
+ clone->cl_dst_pos, clone->cl_count, host_err);
+ if (sync_failed) {
+ struct nfsd_net *nn = net_generic(dst->nf_net,
+ nfsd_net_id);
+
+ nfsd_maybe_reset_write_verifier(nn, rqstp, host_err);
+ }
+ }
+ status = nfserrno(host_err);
if (!status && (READ_ONCE(dst->nf_file->f_mode) & FMODE_NOCMTIME) != 0)
nfsd_update_cmtime_attr(dst->nf_file, 0);
@@ -1653,13 +1767,6 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
#ifdef CONFIG_NFSD_V4_2_INTER_SSC
-extern struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
- struct nfs_fh *src_fh,
- nfs4_stateid *stateid);
-extern void nfs42_ssc_close(struct file *filep);
-
-extern void nfs_sb_deactive(struct super_block *sb);
-
#define NFSD42_INTERSSC_MOUNTOPS "vers=4.2,addr=%s,sec=sys"
/*
@@ -1882,7 +1989,7 @@ nfsd4_cleanup_inter_ssc(struct nfsd4_ssc_umount_item *nsui, struct file *filp,
struct nfsd_net *nn = net_generic(dst->nf_net, nfsd_net_id);
long timeout = msecs_to_jiffies(nfsd4_ssc_umount_timeout);
- nfs42_ssc_close(filp);
+ nfsd42_ssc_close(filp);
fput(filp);
spin_lock(&nn->nfsd_ssc_lock);
@@ -1914,12 +2021,6 @@ nfsd4_cleanup_inter_ssc(struct nfsd4_ssc_umount_item *nsui, struct file *filp,
{
}
-static struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
- struct nfs_fh *src_fh,
- nfs4_stateid *stateid)
-{
- return NULL;
-}
#endif /* CONFIG_NFSD_V4_2_INTER_SSC */
static __be32
@@ -1974,7 +2075,7 @@ static void nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
{
copy->cp_res.wr_stable_how =
test_bit(NFSD4_COPY_F_COMMITTED, &copy->cp_flags) ?
- NFS_FILE_SYNC : NFS_UNSTABLE;
+ FILE_SYNC4 : UNSTABLE4;
nfsd4_copy_set_sync(copy, sync);
}
@@ -2142,8 +2243,8 @@ static int nfsd4_do_async_copy(void *data)
if (nfsd4_ssc_is_inter(copy)) {
struct file *filp;
- filp = nfs42_ssc_open(copy->ss_nsui->nsui_vfsmount,
- &copy->c_fh, &copy->stateid);
+ filp = nfsd42_ssc_open(copy->ss_nsui->nsui_vfsmount,
+ &copy->c_fh, &copy->stateid);
if (IS_ERR(filp)) {
switch (PTR_ERR(filp)) {
case -EBADF:
@@ -3328,7 +3429,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
if (current_fh->fh_export &&
need_wrongsec_check(rqstp))
- op->status = check_nfsd_access(current_fh->fh_export, rqstp, false);
+ op->status = check_nfsd_access(current_fh->fh_export, rqstp);
}
encode_op:
if (op->status == nfserr_replay_me) {
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
index d513971fb119..aee3a0b22d1c 100644
--- a/fs/nfsd/nfs4recover.c
+++ b/fs/nfsd/nfs4recover.c
@@ -47,6 +47,7 @@
#include <linux/nfsd/cld.h>
#include "nfsd.h"
+#include "nfs4ctl.h"
#include "state.h"
#include "vfs.h"
#include "netns.h"
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 9c4adf3110ae..06e4192bc693 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -45,13 +45,15 @@
#include <linux/string_helpers.h>
#include <linux/fsnotify.h>
#include <linux/rhashtable.h>
-#include <linux/nfs_ssc.h>
+#include <linux/nfsd_ssc.h>
#include "xdr4.h"
+#include "nfs4ctl.h"
#include "xdr4cb.h"
#include "vfs.h"
#include "current_stateid.h"
#include "stats.h"
+#include "nfserr.h"
#include "netns.h"
#include "pnfs.h"
@@ -92,6 +94,8 @@ static void nfsd4_end_grace(struct nfsd_net *nn);
static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps);
static void nfsd4_file_hash_remove(struct nfs4_file *fi);
static void deleg_reaper(struct nfsd_net *nn);
+static void nfsd4_drop_revoked_stid(struct nfs4_stid *s)
+ __releases(&s->sc_client->cl_lock);
static const struct lease_manager_operations nfsd_lease_mng_ops;
@@ -1163,6 +1167,8 @@ static void nfs4_free_deleg(struct nfs4_stid *stid)
WARN_ON_ONCE(!list_empty(&dp->dl_perfile));
WARN_ON_ONCE(!list_empty(&dp->dl_perclnt));
WARN_ON_ONCE(!list_empty(&dp->dl_recall_lru));
+ /* The list outlives one recall, so ->release() cannot free it. */
+ nfsd41_cb_destroy_referring_call_list(&dp->dl_recall);
kmem_cache_free(deleg_slab, stid);
atomic_long_dec(&num_delegations);
}
@@ -1279,6 +1285,9 @@ __alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
dp->dl_type = dl_type;
dp->dl_retries = 1;
dp->dl_recalled = false;
+ dp->dl_recall_rejected = false;
+ dp->dl_recall_grant.valid = false;
+ dp->dl_recall_grant.retired_at_send = false;
get_nfs4_file(fp);
dp->dl_stid.sc_file = fp;
nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
@@ -1314,6 +1323,7 @@ static void nfs4_free_dir_deleg(struct nfs4_stid *stid)
for (i = 0; i < ncn->ncn_evt_cnt; ++i)
nfsd_notify_event_put(ncn->ncn_evt[i]);
kfree(ncn->ncn_nf);
+ kfree(ncn->ncn_masks);
for (i = 0; i < NOTIFY4_PAGE_ARRAY_SIZE; i++) {
if (!ncn->ncn_pages[i])
break;
@@ -1346,6 +1356,11 @@ alloc_init_dir_deleg(struct nfs4_client *clp, struct nfs4_file *fp)
nfs4_put_stid(&dp->dl_stid);
return NULL;
}
+ ncn->ncn_masks = kcalloc(NOTIFY4_EVENT_QUEUE_SIZE, sizeof(*ncn->ncn_masks), GFP_KERNEL);
+ if (!ncn->ncn_masks) {
+ nfs4_put_stid(&dp->dl_stid);
+ return NULL;
+ }
spin_lock_init(&ncn->ncn_lock);
nfsd4_init_cb(&ncn->ncn_cb, dp->dl_stid.sc_client,
&nfsd4_cb_notify_ops, NFSPROC4_CLNT_CB_NOTIFY);
@@ -1563,27 +1578,22 @@ static void destroy_delegation(struct nfs4_delegation *dp)
}
/**
- * revoke_delegation - perform nfs4 delegation structure cleanup
- * @dp: pointer to the delegation
+ * revoke_delegation - dispose of a delegation the server has revoked
+ * @dp: delegation to dispose of
+ *
+ * The caller holds a reference on @dp, which this function consumes.
+ * On NFSv4.1 and newer, @dp's sc_status must already carry
+ * SC_STATUS_REVOKED or SC_STATUS_ADMIN_REVOKED.
*
- * This function assumes that it's called either from the administrative
- * interface (nfsd4_revoke_states()) that's revoking a specific delegation
- * stateid or it's called from a laundromat thread (nfsd4_landromat()) that
- * determined that this specific state has expired and needs to be revoked
- * (both mark state with the appropriate stid sc_status mode). It is also
- * assumed that a reference was taken on the @dp state. This function
- * consumes that reference.
+ * @dp is parked on the client's cl_revoked list to await a FREE_STATEID.
+ * Where none can arrive, @dp is destroyed here instead: FREE_STATEID has
+ * already freed it, or the client rejected the recall with
+ * NFS4ERR_BADHANDLE or NFS4ERR_BAD_STATEID and holds no record of the
+ * delegation. NFS4ERR_ADMIN_REVOKED still prompts one, so an
+ * administrative revoke waits on cl_revoked.
*
- * If this function finds that the @dp state is SC_STATUS_FREED it means
- * that a FREE_STATEID operation for this stateid has been processed and
- * we can proceed to removing it from recalled list. However, if @dp state
- * isn't marked SC_STATUS_FREED, it means we need place it on the cl_revoked
- * list and wait for the FREE_STATEID to arrive from the client. At the same
- * time, we need to mark it as SC_STATUS_FREEABLE to indicate to the
- * nfsd4_free_stateid() function that this stateid has already been added
- * to the cl_revoked list and that nfsd4_free_stateid() is now responsible
- * for removing it from the list. Inspection of where the delegation state
- * in the revocation process is protected by the clp->cl_lock.
+ * Context: Takes and releases the client's cl_lock; may sleep after
+ * dropping it.
*/
static void revoke_delegation(struct nfs4_delegation *dp)
{
@@ -1601,6 +1611,19 @@ static void revoke_delegation(struct nfs4_delegation *dp)
list_del_init(&dp->dl_recall_lru);
goto out;
}
+ if (dp->dl_recall_rejected &&
+ !(dp->dl_stid.sc_status & SC_STATUS_ADMIN_REVOKED)) {
+ /*
+ * SC_STATUS_CLOSED, set under cl_lock, makes a racing
+ * FREE_STATEID bail out rather than drop this reference
+ * too. The put releases what cl_revoked would have held.
+ */
+ dp->dl_stid.sc_status |= SC_STATUS_CLOSED;
+ spin_unlock(&clp->cl_lock);
+ nfs4_put_stid(&dp->dl_stid);
+ destroy_unhashed_deleg(dp);
+ return;
+ }
list_add(&dp->dl_recall_lru, &clp->cl_revoked);
dp->dl_stid.sc_status |= SC_STATUS_FREEABLE;
out:
@@ -2789,10 +2812,16 @@ void nfsd4_put_client(struct nfs4_client *clp)
static void
free_client(struct nfs4_client *clp)
{
- while (!list_empty(&clp->cl_sessions)) {
+ LIST_HEAD(reaplist);
+
+ /* client_info_show() walks cl_sessions under cl_lock */
+ spin_lock(&clp->cl_lock);
+ list_splice_init(&clp->cl_sessions, &reaplist);
+ spin_unlock(&clp->cl_lock);
+ while (!list_empty(&reaplist)) {
struct nfsd4_session *ses;
- ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
- se_perclnt);
+ ses = list_entry(reaplist.next, struct nfsd4_session,
+ se_perclnt);
list_del(&ses->se_perclnt);
WARN_ON_ONCE(atomic_read(&ses->se_ref));
free_session(ses);
@@ -2889,11 +2918,18 @@ __destroy_client(struct nfs4_client *clp)
list_del_init(&dp->dl_recall_lru);
destroy_unhashed_deleg(dp);
}
+ /*
+ * A CB_RECALL reply can release revoked delegations concurrently:
+ * nfsd4_shutdown_callback() has not run yet.
+ */
+ spin_lock(&clp->cl_lock);
while (!list_empty(&clp->cl_revoked)) {
dp = list_entry(clp->cl_revoked.next, struct nfs4_delegation, dl_recall_lru);
- list_del_init(&dp->dl_recall_lru);
- nfs4_put_stid(&dp->dl_stid);
+ /* this function drops ->cl_lock */
+ nfsd4_drop_revoked_stid(&dp->dl_stid);
+ spin_lock(&clp->cl_lock);
}
+ spin_unlock(&clp->cl_lock);
while (!list_empty(&clp->cl_openowners)) {
oo = list_entry(clp->cl_openowners.next, struct nfs4_openowner, oo_perclient);
nfs4_get_stateowner(&oo->oo_owner);
@@ -3737,14 +3773,9 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
struct nfsd_notify_event *nne = events[i];
if (!error) {
- u32 *maskp = (u32 *)xdr_reserve_space(&stream, sizeof(*maskp));
+ u32 *maskp = &ncn->ncn_masks[i];
u8 *p;
- if (!maskp) {
- error = true;
- goto put_event;
- }
-
p = nfsd4_encode_notify_event(&stream, nne, dp, nf, maskp);
if (!p) {
pr_notice("Could not generate CB_NOTIFY from fsnotify mask 0x%x\n",
@@ -3762,13 +3793,10 @@ put_event:
nfsd_notify_event_put(nne);
}
if (!error && (dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS))) {
- u32 *maskp = (u32 *)xdr_reserve_space(&stream, sizeof(*maskp));
+ u32 *maskp = &ncn->ncn_masks[count];
u8 *p;
- if (maskp)
- p = nfsd4_encode_dir_attr_change(&stream, dp, nf);
- else
- p = ERR_PTR(-ENOBUFS);
+ p = nfsd4_encode_dir_attr_change(&stream, dp, nf);
if (IS_ERR(p)) {
/*
@@ -6063,6 +6091,80 @@ bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode)
return timeo > 0;
}
+/*
+ * gen_sessionid() composes a sessionid from the client's clientid and a
+ * sequence counter, so the sequence alone identifies the granting session.
+ */
+static void nfsd4_recall_grant_sessionid(const struct nfs4_delegation *dp,
+ struct nfsd4_sessionid *sid)
+{
+ sid->clientid = dp->dl_stid.sc_client->cl_clientid;
+ sid->sequence = dp->dl_recall_grant.sessionid_seq;
+ sid->reserved = 0;
+}
+
+static bool nfsd4_recall_grant_slot_retired(struct nfs4_delegation *dp)
+{
+ struct nfs4_client *clp = dp->dl_stid.sc_client;
+ struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+ struct nfsd4_session *ses;
+ struct nfsd4_sessionid sid;
+ bool retired = false;
+ void *entry;
+
+ if (!dp->dl_recall_grant.valid)
+ return false;
+
+ nfsd4_recall_grant_sessionid(dp, &sid);
+
+ /*
+ * A missing session does not prove the client saw the grant: a
+ * DESTROY_SESSION unhashes its own session before the reply to
+ * that compound is encoded.
+ */
+ spin_lock(&nn->client_lock);
+ ses = __find_in_sessionid_hashtbl((struct nfs4_sessionid *)&sid,
+ clp->net);
+ entry = ses ? xa_load(&ses->se_slots, dp->dl_recall_grant.slotid) : NULL;
+ if (xa_is_value(entry)) {
+ /*
+ * A slot is freed only once the client has acknowledged
+ * the smaller slot table, which it cannot do while a
+ * request on that slot is outstanding.
+ */
+ retired = true;
+ } else if (entry) {
+ struct nfsd4_slot *slot = entry;
+
+ /*
+ * A reactivated slot was freed and rebuilt, so the same
+ * acknowledgment applies. The seqid test errs toward
+ * revoking: a rebuilt slot restarting at seqid 1 matches
+ * an old grant.
+ */
+ retired = (slot->sl_flags & NFSD4_SLOT_REUSED) ||
+ ((slot->sl_flags & NFSD4_SLOT_INITIALIZED) &&
+ slot->sl_seqid != dp->dl_recall_grant.seqid);
+ }
+ spin_unlock(&nn->client_lock);
+ return retired;
+}
+
+/*
+ * ->prepare does not run on every send: nfsd4_run_cb_work() skips it
+ * on a requeue, and a retry via rpc_restart_call_prepare() re-enters
+ * the RPC layer beneath it. The granting request does not change, so
+ * a send inherits a correct list. Retirement is the one transition
+ * the list has to follow.
+ */
+static void nfsd4_refresh_recall_grant(struct nfs4_delegation *dp)
+{
+ dp->dl_recall_grant.retired_at_send =
+ nfsd4_recall_grant_slot_retired(dp);
+ if (dp->dl_recall_grant.retired_at_send)
+ nfsd41_cb_destroy_referring_call_list(&dp->dl_recall);
+}
+
static bool nfsd4_cb_recall_prepare(struct nfsd4_callback *cb)
{
struct nfs4_delegation *dp = cb_to_delegation(cb);
@@ -6084,9 +6186,46 @@ static bool nfsd4_cb_recall_prepare(struct nfsd4_callback *cb)
list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
}
spin_unlock(&nn->deleg_lock);
+
+ nfsd4_refresh_recall_grant(dp);
+
+ if (dp->dl_recall_grant.valid && !dp->dl_recall_grant.retired_at_send) {
+ struct nfsd4_sessionid sid;
+
+ nfsd4_recall_grant_sessionid(dp, &sid);
+ nfsd41_cb_referring_call(&dp->dl_recall,
+ (struct nfs4_sessionid *)&sid,
+ dp->dl_recall_grant.slotid,
+ dp->dl_recall_grant.seqid);
+ }
return true;
}
+/*
+ * cl_lock orders this against a laundromat reaping @dp: either
+ * revoke_delegation() observes dl_recall_rejected and destroys @dp, or
+ * it reached cl_revoked first and @dp is released here instead.
+ */
+static void nfsd4_deleg_recall_rejected(struct nfs4_delegation *dp)
+{
+ struct nfs4_client *clp = dp->dl_stid.sc_client;
+
+ spin_lock(&clp->cl_lock);
+ if (dp->dl_stid.sc_status & (SC_STATUS_CLOSED | SC_STATUS_FREED |
+ SC_STATUS_ADMIN_REVOKED)) {
+ spin_unlock(&clp->cl_lock);
+ return;
+ }
+ if (dp->dl_stid.sc_status & SC_STATUS_FREEABLE) {
+ dp->dl_stid.sc_status |= SC_STATUS_CLOSED;
+ /* this function drops ->cl_lock */
+ nfsd4_drop_revoked_stid(&dp->dl_stid);
+ return;
+ }
+ dp->dl_recall_rejected = true;
+ spin_unlock(&clp->cl_lock);
+}
+
static int nfsd4_cb_recall_done(struct nfsd4_callback *cb,
struct rpc_task *task)
{
@@ -6094,27 +6233,32 @@ static int nfsd4_cb_recall_done(struct nfsd4_callback *cb,
trace_nfsd_cb_recall_done(&dp->dl_stid.sc_stateid, task);
- if (dp->dl_stid.sc_status)
- /* CLOSED or REVOKED */
- return 1;
-
switch (task->tk_status) {
case 0:
return 1;
case -NFS4ERR_DELAY:
+ if (dp->dl_stid.sc_status)
+ /* CLOSED or REVOKED */
+ return 1;
rpc_delay(task, 2 * HZ);
return 0;
case -EBADHANDLE:
case -NFS4ERR_BAD_STATEID:
/*
- * Race: client probably got cb_recall before open reply
- * granting delegation.
+ * Retirement of the granting slot proves the client saw
+ * the grant. Trust the rejection only if the slot had
+ * retired when this recall was sent.
*/
- if (dp->dl_retries--) {
+ if (dp->dl_recall_grant.retired_at_send) {
+ nfsd4_deleg_recall_rejected(dp);
+ return 1;
+ }
+ if (!dp->dl_stid.sc_status && dp->dl_retries--) {
+ nfsd4_refresh_recall_grant(dp);
rpc_delay(task, 2 * HZ);
return 0;
}
- fallthrough;
+ return 1;
default:
return 1;
}
@@ -6708,9 +6852,25 @@ static bool nfsd4_want_deleg_timestamps(const struct nfsd4_open *open)
return open->op_deleg_want & OPEN4_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS;
}
+static void
+nfs4_delegation_record_grant_slot(struct nfs4_delegation *dp,
+ const struct nfsd4_compound_state *cstate)
+{
+ const struct nfsd4_sessionid *sid;
+
+ if (!cstate->session)
+ return;
+ sid = (struct nfsd4_sessionid *)cstate->session->se_sessionid.data;
+ dp->dl_recall_grant.sessionid_seq = sid->sequence;
+ dp->dl_recall_grant.slotid = cstate->slot->sl_index;
+ dp->dl_recall_grant.seqid = cstate->slot->sl_seqid;
+ dp->dl_recall_grant.valid = true;
+}
+
static struct nfs4_delegation *
-nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
- struct svc_fh *parent)
+nfs4_set_delegation(struct nfsd4_open *open,
+ const struct nfsd4_compound_state *cstate,
+ struct nfs4_ol_stateid *stp, struct svc_fh *parent)
{
bool deleg_ts = nfsd4_want_deleg_timestamps(open);
struct nfs4_client *clp = stp->st_stid.sc_client;
@@ -6800,6 +6960,14 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
dp = alloc_init_deleg(clp, fp, odstate, dl_type);
if (!dp)
goto out_delegees;
+
+ /*
+ * Record the granting slot before kernel_setlease() makes @dp
+ * visible to lease breakers. A conflicting open can drive
+ * CB_RECALL to completion from that point on.
+ */
+ nfs4_delegation_record_grant_slot(dp, cstate);
+
if (stp->st_stid.sc_export)
dp->dl_stid.sc_export = exp_get(stp->st_stid.sc_export);
@@ -6964,6 +7132,7 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open,
struct nfs4_ol_stateid *stp, struct svc_fh *currentfh,
struct svc_fh *fh)
{
+ struct nfsd4_compoundres *resp = rqstp->rq_resp;
struct nfs4_openowner *oo = openowner(stp->st_stateowner);
bool deleg_ts = nfsd4_want_deleg_timestamps(open);
struct nfs4_client *clp = stp->st_stid.sc_client;
@@ -7000,7 +7169,7 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open,
default:
goto out_no_deleg;
}
- dp = nfs4_set_delegation(open, stp, parent);
+ dp = nfs4_set_delegation(open, &resp->cstate, stp, parent);
if (IS_ERR(dp))
goto out_no_deleg;
@@ -10294,6 +10463,7 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
dp = alloc_init_dir_deleg(clp, fp);
if (!dp)
goto out_delegees;
+ nfs4_delegation_record_grant_slot(dp, cstate);
if (cstate->current_fh.fh_export)
dp->dl_stid.sc_export =
exp_get(cstate->current_fh.fh_export);
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 606ddcb085c0..7d1b2d6f57f2 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -54,6 +54,7 @@
#include "xdr4.h"
#include "vfs.h"
#include "state.h"
+#include "nfserr.h"
#include "cache.h"
#include "netns.h"
#include "pnfs.h"
@@ -1606,7 +1607,7 @@ nfsd4_decode_write(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u)
return nfserr_bad_xdr;
if (xdr_stream_decode_u32(argp->xdr, &write->wr_stable_how) < 0)
return nfserr_bad_xdr;
- if (write->wr_stable_how > NFS_FILE_SYNC)
+ if (write->wr_stable_how > FILE_SYNC4)
return nfserr_bad_xdr;
if (xdr_stream_decode_u32(argp->xdr, &write->wr_buflen) < 0)
return nfserr_bad_xdr;
@@ -1920,6 +1921,17 @@ nfsd4_decode_get_dir_delegation(struct nfsd4_compoundargs *argp,
#ifdef CONFIG_NFSD_PNFS
static __be32
+nfsd4_decode_deviceid4(struct xdr_stream *xdr, struct nfsd4_deviceid *devid)
+{
+ __be32 *p = xdr_inline_decode(xdr, NFS4_DEVICEID4_SIZE);
+
+ if (unlikely(!p))
+ return nfserr_bad_xdr;
+ svcxdr_decode_deviceid4(p, devid);
+ return nfs_ok;
+}
+
+static __be32
nfsd4_decode_getdeviceinfo(struct nfsd4_compoundargs *argp,
union nfsd4_op_u *u)
{
@@ -2733,6 +2745,87 @@ nfsd4_decode_compound(struct nfsd4_compoundargs *argp)
return true;
}
+static __always_inline __be32
+nfsd4_encode_bool(struct xdr_stream *xdr, bool val)
+{
+ __be32 *p = xdr_reserve_space(xdr, XDR_UNIT);
+
+ if (unlikely(p == NULL))
+ return nfserr_resource;
+ *p = val ? xdr_one : xdr_zero;
+ return nfs_ok;
+}
+
+static __always_inline __be32
+nfsd4_encode_uint32_t(struct xdr_stream *xdr, u32 val)
+{
+ __be32 *p = xdr_reserve_space(xdr, XDR_UNIT);
+
+ if (unlikely(p == NULL))
+ return nfserr_resource;
+ *p = cpu_to_be32(val);
+ return nfs_ok;
+}
+
+#define nfsd4_encode_aceflag4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_acemask4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_acetype4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_count4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_mode4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_nfs_lease4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_qop4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_sequenceid4(x, v) nfsd4_encode_uint32_t(x, v)
+#define nfsd4_encode_slotid4(x, v) nfsd4_encode_uint32_t(x, v)
+
+static __always_inline __be32
+nfsd4_encode_uint64_t(struct xdr_stream *xdr, u64 val)
+{
+ __be32 *p = xdr_reserve_space(xdr, XDR_UNIT * 2);
+
+ if (unlikely(p == NULL))
+ return nfserr_resource;
+ put_unaligned_be64(val, p);
+ return nfs_ok;
+}
+
+#define nfsd4_encode_changeid4(x, v) nfsd4_encode_uint64_t(x, v)
+#define nfsd4_encode_nfs_cookie4(x, v) nfsd4_encode_uint64_t(x, v)
+#define nfsd4_encode_length4(x, v) nfsd4_encode_uint64_t(x, v)
+#define nfsd4_encode_offset4(x, v) nfsd4_encode_uint64_t(x, v)
+
+static __always_inline __be32
+nfsd4_encode_opaque_fixed(struct xdr_stream *xdr, const void *data,
+ size_t size)
+{
+ __be32 *p = xdr_reserve_space(xdr, xdr_align_size(size));
+ size_t pad = xdr_pad_size(size);
+
+ if (unlikely(p == NULL))
+ return nfserr_resource;
+ memcpy(p, data, size);
+ if (pad)
+ memset((char *)p + size, 0, pad);
+ return nfs_ok;
+}
+
+static __always_inline __be32
+nfsd4_encode_opaque(struct xdr_stream *xdr, const void *data, size_t size)
+{
+ size_t pad = xdr_pad_size(size);
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, XDR_UNIT + xdr_align_size(size));
+ if (unlikely(p == NULL))
+ return nfserr_resource;
+ *p++ = cpu_to_be32(size);
+ memcpy(p, data, size);
+ if (pad)
+ memset((char *)p + size, 0, pad);
+ return nfs_ok;
+}
+
+#define nfsd4_encode_component4(x, d, s) nfsd4_encode_opaque(x, d, s)
+
static __be32 nfsd4_encode_nfs_fh4(struct xdr_stream *xdr,
const struct knfsd_fh *fh_handle)
{
@@ -4285,21 +4378,16 @@ out:
static bool
nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
struct dentry *dentry, struct nfs4_delegation *dp,
- struct nfsd_file *nf, char *name, u32 namelen)
+ struct nfsd_file *nf, char *name, u32 namelen,
+ u32 *attrmask)
{
struct path path = nf->nf_file->f_path;
struct nfsd4_fattr_args args = { };
const u32 *reqmask;
- uint32_t *attrmask;
__be32 status;
bool parent;
int ret;
- /* Reserve space for attrmask */
- attrmask = xdr_reserve_space(xdr, 3 * sizeof(uint32_t));
- if (!attrmask)
- return false;
-
ne->ne_file.data = name;
ne->ne_file.len = namelen;
ne->ne_attrs.attrmask.element = attrmask;
@@ -4383,6 +4471,7 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *
struct nfs4_delegation *dp, struct nfsd_file *nf,
u32 *notify_mask)
{
+ u32 attrmask[3][3] = { };
u8 *p = NULL;
*notify_mask = 0;
@@ -4391,7 +4480,8 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *
struct notify_remove4 nr = { };
if (!nfsd4_setup_notify_entry4(&nr.nrm_old_entry, xdr, nne->ne_dentry, dp,
- nf, nne->ne_name, nne->ne_namelen))
+ nf, nne->ne_name, nne->ne_namelen,
+ attrmask[0]))
goto out_err;
p = (u8 *)xdr->p;
if (!xdrgen_encode_notify_remove4(xdr, &nr))
@@ -4402,14 +4492,16 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *
struct notify_remove4 old = { };
if (!nfsd4_setup_notify_entry4(&na.nad_new_entry, xdr, nne->ne_dentry, dp,
- nf, nne->ne_name, nne->ne_namelen))
+ nf, nne->ne_name, nne->ne_namelen,
+ attrmask[0]))
goto out_err;
/* If a file was overwritten, report it in nad_old_entry */
if (nne->ne_target) {
if (!nfsd4_setup_notify_entry4(&old.nrm_old_entry, xdr,
NULL, dp, nf,
- nne->ne_name, nne->ne_namelen))
+ nne->ne_name, nne->ne_namelen,
+ attrmask[1]))
goto out_err;
na.nad_old_entry.count = 1;
na.nad_old_entry.element = &old;
@@ -4428,19 +4520,19 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *
/* Don't send any attributes in the old_entry since they're the same in new */
if (!nfsd4_setup_notify_entry4(&nr.nrn_old_entry.nrm_old_entry, xdr,
NULL, dp, nf, nne->ne_name,
- nne->ne_namelen))
+ nne->ne_namelen, attrmask[0]))
goto out_err;
if (!nfsd4_setup_notify_entry4(&nr.nrn_new_entry.nad_new_entry, xdr,
nne->ne_dentry, dp, nf, newname,
- nne->ne_newnamelen))
+ nne->ne_newnamelen, attrmask[1]))
goto out_err;
/* If a file was overwritten, report it in nad_old_entry */
if (nne->ne_target) {
if (!nfsd4_setup_notify_entry4(&old.nrm_old_entry, xdr,
NULL, dp, nf, newname,
- nne->ne_newnamelen))
+ nne->ne_newnamelen, attrmask[2]))
goto out_err;
nr.nrn_new_entry.nad_old_entry.count = 1;
nr.nrn_new_entry.nad_old_entry.element = &old;
@@ -4476,11 +4568,12 @@ u8 *nfsd4_encode_dir_attr_change(struct xdr_stream *xdr, struct nfs4_delegation
{
struct dentry *dentry = nf->nf_file->f_path.dentry;
struct notify_attr4 na = { };
+ u32 attrmask[3] = { };
u8 *p;
/* RFC 8881 s10.4.3: ne_file must be a zero-length string for dir attrs */
if (!nfsd4_setup_notify_entry4(&na.na_changed_entry, xdr,
- dentry, dp, nf, "", 0))
+ dentry, dp, nf, "", 0, attrmask))
return ERR_PTR(-ENOBUFS);
/* No requested attributes to report; omit the event */
@@ -4574,8 +4667,6 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name,
* directly from the mountpoint dentry.
*/
if (nfsd_mountpoint(dentry, exp)) {
- int err;
-
if (!(exp->ex_flags & NFSEXP_V4ROOT)
&& !attributes_need_mount(cd->rd_bmval)) {
ignore_crossmnt = 1;
@@ -4586,12 +4677,7 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name,
* Different "."/".." handling? Something else?
* At least, add a comment here to explain....
*/
- err = nfsd_cross_mnt(cd->rd_rqstp, &dentry, &exp);
- if (err) {
- nfserr = nfserrno(err);
- goto out_put;
- }
- nfserr = check_nfsd_access(exp, cd->rd_rqstp, false);
+ nfserr = nfsd_cross_mnt(cd->rd_rqstp, &dentry, &exp);
if (nfserr)
goto out_put;
crossed = true;
diff --git a/fs/nfsd/nfscache.c b/fs/nfsd/nfscache.c
index c7db532c8523..80364b91331a 100644
--- a/fs/nfsd/nfscache.c
+++ b/fs/nfsd/nfscache.c
@@ -19,6 +19,7 @@
#include <net/checksum.h>
#include "nfsd.h"
+#include "nfserr.h"
#include "netns.h"
#include "stats.h"
#include "cache.h"
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 5abb2d4274c9..c0f10517470b 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -23,6 +23,8 @@
#include "idmap.h"
#include "nfsd.h"
+#include "nfserr.h"
+#include "nfs4ctl.h"
#include "netns.h"
#include "stats.h"
#include "cache.h"
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index 76a69d9a4e73..64315890eef5 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -15,7 +15,6 @@
#include <linux/nfs.h>
#include <linux/nfs2.h>
#include <linux/nfs3.h>
-#include <linux/nfs4.h>
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svc_xprt.h>
@@ -147,49 +146,7 @@ extern u64 nfsd_io_cache_write __read_mostly;
extern int nfsd_max_blksize;
-static inline int nfsd_v4client(struct svc_rqst *rq)
-{
- return rq && rq->rq_prog == NFS_PROGRAM && rq->rq_vers == 4;
-}
-
-/*
- * NFSv4 State
- */
-#ifdef CONFIG_NFSD_V4
-extern unsigned long max_delegations;
-int nfsd4_init_slabs(void);
-void nfsd4_free_slabs(void);
-int nfs4_state_start(void);
-int nfs4_state_start_net(struct net *net);
-void nfs4_state_shutdown(void);
-void nfs4_state_shutdown_net(struct net *net);
-int nfs4_reset_recoverydir(char *recdir);
-char * nfs4_recoverydir(void);
-bool nfsd4_spo_must_allow(struct svc_rqst *rqstp);
-int nfsd4_create_laundry_wq(void);
-void nfsd4_destroy_laundry_wq(void);
-bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode);
-#else
-static inline int nfsd4_init_slabs(void) { return 0; }
-static inline void nfsd4_free_slabs(void) { }
-static inline int nfs4_state_start(void) { return 0; }
-static inline int nfs4_state_start_net(struct net *net) { return 0; }
-static inline void nfs4_state_shutdown(void) { }
-static inline void nfs4_state_shutdown_net(struct net *net) { }
-static inline int nfs4_reset_recoverydir(char *recdir) { return 0; }
-static inline char * nfs4_recoverydir(void) {return NULL; }
-static inline bool nfsd4_spo_must_allow(struct svc_rqst *rqstp)
-{
- return false;
-}
-static inline int nfsd4_create_laundry_wq(void) { return 0; };
-static inline void nfsd4_destroy_laundry_wq(void) {};
-static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp,
- struct inode *inode)
-{
- return false;
-}
-#endif
+bool nfsd_v4client(struct svc_rqst *rqstp);
/*
* lockd binding
@@ -198,195 +155,4 @@ void nfsd_lockd_init(void);
void nfsd_lockd_shutdown(void);
-/*
- * These macros provide pre-xdr'ed values for faster operation.
- */
-#define nfs_ok cpu_to_be32(NFS_OK)
-#define nfserr_perm cpu_to_be32(NFSERR_PERM)
-#define nfserr_noent cpu_to_be32(NFSERR_NOENT)
-#define nfserr_io cpu_to_be32(NFSERR_IO)
-#define nfserr_nxio cpu_to_be32(NFSERR_NXIO)
-#define nfserr_acces cpu_to_be32(NFSERR_ACCES)
-#define nfserr_exist cpu_to_be32(NFSERR_EXIST)
-#define nfserr_xdev cpu_to_be32(NFSERR_XDEV)
-#define nfserr_nodev cpu_to_be32(NFSERR_NODEV)
-#define nfserr_notdir cpu_to_be32(NFSERR_NOTDIR)
-#define nfserr_isdir cpu_to_be32(NFSERR_ISDIR)
-#define nfserr_inval cpu_to_be32(NFSERR_INVAL)
-#define nfserr_fbig cpu_to_be32(NFSERR_FBIG)
-#define nfserr_nospc cpu_to_be32(NFSERR_NOSPC)
-#define nfserr_rofs cpu_to_be32(NFSERR_ROFS)
-#define nfserr_mlink cpu_to_be32(NFSERR_MLINK)
-#define nfserr_nametoolong cpu_to_be32(NFSERR_NAMETOOLONG)
-#define nfserr_notempty cpu_to_be32(NFSERR_NOTEMPTY)
-#define nfserr_dquot cpu_to_be32(NFSERR_DQUOT)
-#define nfserr_stale cpu_to_be32(NFSERR_STALE)
-#define nfserr_remote cpu_to_be32(NFSERR_REMOTE)
-#define nfserr_wflush cpu_to_be32(NFSERR_WFLUSH)
-#define nfserr_badhandle cpu_to_be32(NFSERR_BADHANDLE)
-#define nfserr_notsync cpu_to_be32(NFSERR_NOT_SYNC)
-#define nfserr_badcookie cpu_to_be32(NFSERR_BAD_COOKIE)
-#define nfserr_notsupp cpu_to_be32(NFSERR_NOTSUPP)
-#define nfserr_toosmall cpu_to_be32(NFSERR_TOOSMALL)
-#define nfserr_serverfault cpu_to_be32(NFSERR_SERVERFAULT)
-#define nfserr_badtype cpu_to_be32(NFSERR_BADTYPE)
-#define nfserr_jukebox cpu_to_be32(NFSERR_JUKEBOX)
-#define nfserr_denied cpu_to_be32(NFSERR_DENIED)
-#define nfserr_deadlock cpu_to_be32(NFSERR_DEADLOCK)
-#define nfserr_expired cpu_to_be32(NFSERR_EXPIRED)
-#define nfserr_bad_cookie cpu_to_be32(NFSERR_BAD_COOKIE)
-#define nfserr_same cpu_to_be32(NFSERR_SAME)
-#define nfserr_clid_inuse cpu_to_be32(NFSERR_CLID_INUSE)
-#define nfserr_stale_clientid cpu_to_be32(NFSERR_STALE_CLIENTID)
-#define nfserr_resource cpu_to_be32(NFSERR_RESOURCE)
-#define nfserr_moved cpu_to_be32(NFSERR_MOVED)
-#define nfserr_nofilehandle cpu_to_be32(NFSERR_NOFILEHANDLE)
-#define nfserr_minor_vers_mismatch cpu_to_be32(NFSERR_MINOR_VERS_MISMATCH)
-#define nfserr_share_denied cpu_to_be32(NFSERR_SHARE_DENIED)
-#define nfserr_stale_stateid cpu_to_be32(NFSERR_STALE_STATEID)
-#define nfserr_old_stateid cpu_to_be32(NFSERR_OLD_STATEID)
-#define nfserr_bad_stateid cpu_to_be32(NFSERR_BAD_STATEID)
-#define nfserr_bad_seqid cpu_to_be32(NFSERR_BAD_SEQID)
-#define nfserr_symlink cpu_to_be32(NFSERR_SYMLINK)
-#define nfserr_not_same cpu_to_be32(NFSERR_NOT_SAME)
-#define nfserr_lock_range cpu_to_be32(NFSERR_LOCK_RANGE)
-#define nfserr_restorefh cpu_to_be32(NFSERR_RESTOREFH)
-#define nfserr_attrnotsupp cpu_to_be32(NFSERR_ATTRNOTSUPP)
-#define nfserr_bad_xdr cpu_to_be32(NFSERR_BAD_XDR)
-#define nfserr_openmode cpu_to_be32(NFSERR_OPENMODE)
-#define nfserr_badowner cpu_to_be32(NFSERR_BADOWNER)
-#define nfserr_locks_held cpu_to_be32(NFSERR_LOCKS_HELD)
-#define nfserr_op_illegal cpu_to_be32(NFSERR_OP_ILLEGAL)
-#define nfserr_grace cpu_to_be32(NFSERR_GRACE)
-#define nfserr_no_grace cpu_to_be32(NFSERR_NO_GRACE)
-#define nfserr_reclaim_bad cpu_to_be32(NFSERR_RECLAIM_BAD)
-#define nfserr_badname cpu_to_be32(NFSERR_BADNAME)
-#define nfserr_admin_revoked cpu_to_be32(NFS4ERR_ADMIN_REVOKED)
-#define nfserr_cb_path_down cpu_to_be32(NFSERR_CB_PATH_DOWN)
-#define nfserr_locked cpu_to_be32(NFSERR_LOCKED)
-#define nfserr_wrongsec cpu_to_be32(NFSERR_WRONGSEC)
-#define nfserr_delay cpu_to_be32(NFS4ERR_DELAY)
-#define nfserr_badiomode cpu_to_be32(NFS4ERR_BADIOMODE)
-#define nfserr_badlayout cpu_to_be32(NFS4ERR_BADLAYOUT)
-#define nfserr_bad_session_digest cpu_to_be32(NFS4ERR_BAD_SESSION_DIGEST)
-#define nfserr_badsession cpu_to_be32(NFS4ERR_BADSESSION)
-#define nfserr_badslot cpu_to_be32(NFS4ERR_BADSLOT)
-#define nfserr_complete_already cpu_to_be32(NFS4ERR_COMPLETE_ALREADY)
-#define nfserr_conn_not_bound_to_session cpu_to_be32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION)
-#define nfserr_deleg_already_wanted cpu_to_be32(NFS4ERR_DELEG_ALREADY_WANTED)
-#define nfserr_back_chan_busy cpu_to_be32(NFS4ERR_BACK_CHAN_BUSY)
-#define nfserr_layouttrylater cpu_to_be32(NFS4ERR_LAYOUTTRYLATER)
-#define nfserr_layoutunavailable cpu_to_be32(NFS4ERR_LAYOUTUNAVAILABLE)
-#define nfserr_nomatching_layout cpu_to_be32(NFS4ERR_NOMATCHING_LAYOUT)
-#define nfserr_recallconflict cpu_to_be32(NFS4ERR_RECALLCONFLICT)
-#define nfserr_unknown_layouttype cpu_to_be32(NFS4ERR_UNKNOWN_LAYOUTTYPE)
-#define nfserr_seq_misordered cpu_to_be32(NFS4ERR_SEQ_MISORDERED)
-#define nfserr_sequence_pos cpu_to_be32(NFS4ERR_SEQUENCE_POS)
-#define nfserr_req_too_big cpu_to_be32(NFS4ERR_REQ_TOO_BIG)
-#define nfserr_rep_too_big cpu_to_be32(NFS4ERR_REP_TOO_BIG)
-#define nfserr_rep_too_big_to_cache cpu_to_be32(NFS4ERR_REP_TOO_BIG_TO_CACHE)
-#define nfserr_retry_uncached_rep cpu_to_be32(NFS4ERR_RETRY_UNCACHED_REP)
-#define nfserr_unsafe_compound cpu_to_be32(NFS4ERR_UNSAFE_COMPOUND)
-#define nfserr_too_many_ops cpu_to_be32(NFS4ERR_TOO_MANY_OPS)
-#define nfserr_op_not_in_session cpu_to_be32(NFS4ERR_OP_NOT_IN_SESSION)
-#define nfserr_hash_alg_unsupp cpu_to_be32(NFS4ERR_HASH_ALG_UNSUPP)
-#define nfserr_clientid_busy cpu_to_be32(NFS4ERR_CLIENTID_BUSY)
-#define nfserr_pnfs_io_hole cpu_to_be32(NFS4ERR_PNFS_IO_HOLE)
-#define nfserr_seq_false_retry cpu_to_be32(NFS4ERR_SEQ_FALSE_RETRY)
-#define nfserr_bad_high_slot cpu_to_be32(NFS4ERR_BAD_HIGH_SLOT)
-#define nfserr_deadsession cpu_to_be32(NFS4ERR_DEADSESSION)
-#define nfserr_encr_alg_unsupp cpu_to_be32(NFS4ERR_ENCR_ALG_UNSUPP)
-#define nfserr_pnfs_no_layout cpu_to_be32(NFS4ERR_PNFS_NO_LAYOUT)
-#define nfserr_not_only_op cpu_to_be32(NFS4ERR_NOT_ONLY_OP)
-#define nfserr_wrong_cred cpu_to_be32(NFS4ERR_WRONG_CRED)
-#define nfserr_wrong_type cpu_to_be32(NFS4ERR_WRONG_TYPE)
-#define nfserr_dirdeleg_unavail cpu_to_be32(NFS4ERR_DIRDELEG_UNAVAIL)
-#define nfserr_reject_deleg cpu_to_be32(NFS4ERR_REJECT_DELEG)
-#define nfserr_returnconflict cpu_to_be32(NFS4ERR_RETURNCONFLICT)
-#define nfserr_deleg_revoked cpu_to_be32(NFS4ERR_DELEG_REVOKED)
-#define nfserr_partner_notsupp cpu_to_be32(NFS4ERR_PARTNER_NOTSUPP)
-#define nfserr_partner_no_auth cpu_to_be32(NFS4ERR_PARTNER_NO_AUTH)
-#define nfserr_union_notsupp cpu_to_be32(NFS4ERR_UNION_NOTSUPP)
-#define nfserr_offload_denied cpu_to_be32(NFS4ERR_OFFLOAD_DENIED)
-#define nfserr_wrong_lfs cpu_to_be32(NFS4ERR_WRONG_LFS)
-#define nfserr_badlabel cpu_to_be32(NFS4ERR_BADLABEL)
-#define nfserr_file_open cpu_to_be32(NFS4ERR_FILE_OPEN)
-#define nfserr_xattr2big cpu_to_be32(NFS4ERR_XATTR2BIG)
-#define nfserr_noxattr cpu_to_be32(NFS4ERR_NOXATTR)
-
-/*
- * Error codes for internal use. These are based at an impossible
- * nfsstat4 value so that, once converted to be32, they cannot conflict
- * with any value defined by the protocol (compare the nlm__int__* codes
- * in fs/lockd/lockd.h).
- */
-enum {
-/* end-of-file indicator in readdir */
- NFSERR_EOF = 30000,
-#define nfserr_eof cpu_to_be32(NFSERR_EOF)
-
-/* replay detected */
- NFSERR_REPLAY_ME,
-#define nfserr_replay_me cpu_to_be32(NFSERR_REPLAY_ME)
-
-/* nfs41 replay detected */
- NFSERR_REPLAY_CACHE,
-#define nfserr_replay_cache cpu_to_be32(NFSERR_REPLAY_CACHE)
-
-/* symlink found where dir expected - handled differently to
- * other symlink found errors by NFSv3.
- */
- NFSERR_SYMLINK_NOT_DIR,
-#define nfserr_symlink_not_dir cpu_to_be32(NFSERR_SYMLINK_NOT_DIR)
-};
-
-#ifdef CONFIG_NFSD_V4
-
-/* before processing a COMPOUND operation, we have to check that there
- * is enough space in the buffer for XDR encode to succeed. otherwise,
- * we might process an operation with side effects, and be unable to
- * tell the client that the operation succeeded.
- *
- * COMPOUND_SLACK_SPACE - this is the minimum bytes of buffer space
- * needed to encode an "ordinary" _successful_ operation. (GETATTR,
- * READ, READDIR, and READLINK have their own buffer checks.) if we
- * fall below this level, we fail the next operation with NFS4ERR_RESOURCE.
- *
- * COMPOUND_ERR_SLACK_SPACE - this is the minimum bytes of buffer space
- * needed to encode an operation which has failed with NFS4ERR_RESOURCE.
- * care is taken to ensure that we never fall below this level for any
- * reason.
- */
-#define COMPOUND_SLACK_SPACE 140 /* OP_GETFH */
-#define COMPOUND_ERR_SLACK_SPACE 16 /* OP_SETATTR */
-
-#define NFSD_LAUNDROMAT_MINTIMEOUT 1 /* seconds */
-#define NFSD_COURTESY_CLIENT_TIMEOUT (24 * 60 * 60) /* seconds */
-#define NFSD_CLIENT_MAX_TRIM_PER_RUN 128
-#define NFS4_CLIENTS_PER_GB 1024
-#define NFSD_DELEGRETURN_TIMEOUT (HZ / 34) /* 30ms */
-#define NFSD_CB_GETATTR_TIMEOUT NFSD_DELEGRETURN_TIMEOUT
-
-extern int nfsd4_is_junction(struct dentry *dentry);
-extern int register_cld_notifier(void);
-extern void unregister_cld_notifier(void);
-#ifdef CONFIG_NFSD_V4_2_INTER_SSC
-extern void nfsd4_ssc_init_umount_work(struct nfsd_net *nn);
-#endif
-
-extern void nfsd4_init_leases_net(struct nfsd_net *nn);
-
-#else /* CONFIG_NFSD_V4 */
-static inline int nfsd4_is_junction(struct dentry *dentry)
-{
- return 0;
-}
-
-static inline void nfsd4_init_leases_net(struct nfsd_net *nn) { };
-
-#define register_cld_notifier() 0
-#define unregister_cld_notifier() do { } while(0)
-
-#endif /* CONFIG_NFSD_V4 */
-
#endif /* LINUX_NFSD_NFSD_H */
diff --git a/fs/nfsd/nfserr.h b/fs/nfsd/nfserr.h
new file mode 100644
index 000000000000..9b9df7aab220
--- /dev/null
+++ b/fs/nfsd/nfserr.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Pre-xdr'ed nfsd error values and nfsd-internal error codes.
+ *
+ * Separated from nfsd.h so that nfsd.h itself does not have to pull
+ * in <linux/nfs4.h>: the NFS4ERR_* values used below are the only
+ * reason that include was needed.
+ */
+
+#ifndef LINUX_NFSD_NFSERR_H
+#define LINUX_NFSD_NFSERR_H
+
+#include <linux/nfs.h>
+#include <linux/nfs4.h>
+
+/*
+ * These macros provide pre-xdr'ed values for faster operation.
+ */
+#define nfs_ok cpu_to_be32(NFS_OK)
+#define nfserr_perm cpu_to_be32(NFSERR_PERM)
+#define nfserr_noent cpu_to_be32(NFSERR_NOENT)
+#define nfserr_io cpu_to_be32(NFSERR_IO)
+#define nfserr_nxio cpu_to_be32(NFSERR_NXIO)
+#define nfserr_acces cpu_to_be32(NFSERR_ACCES)
+#define nfserr_exist cpu_to_be32(NFSERR_EXIST)
+#define nfserr_xdev cpu_to_be32(NFSERR_XDEV)
+#define nfserr_nodev cpu_to_be32(NFSERR_NODEV)
+#define nfserr_notdir cpu_to_be32(NFSERR_NOTDIR)
+#define nfserr_isdir cpu_to_be32(NFSERR_ISDIR)
+#define nfserr_inval cpu_to_be32(NFSERR_INVAL)
+#define nfserr_fbig cpu_to_be32(NFSERR_FBIG)
+#define nfserr_nospc cpu_to_be32(NFSERR_NOSPC)
+#define nfserr_rofs cpu_to_be32(NFSERR_ROFS)
+#define nfserr_mlink cpu_to_be32(NFSERR_MLINK)
+#define nfserr_nametoolong cpu_to_be32(NFSERR_NAMETOOLONG)
+#define nfserr_notempty cpu_to_be32(NFSERR_NOTEMPTY)
+#define nfserr_dquot cpu_to_be32(NFSERR_DQUOT)
+#define nfserr_stale cpu_to_be32(NFSERR_STALE)
+#define nfserr_remote cpu_to_be32(NFSERR_REMOTE)
+#define nfserr_wflush cpu_to_be32(NFSERR_WFLUSH)
+#define nfserr_badhandle cpu_to_be32(NFSERR_BADHANDLE)
+#define nfserr_notsync cpu_to_be32(NFSERR_NOT_SYNC)
+#define nfserr_badcookie cpu_to_be32(NFSERR_BAD_COOKIE)
+#define nfserr_notsupp cpu_to_be32(NFSERR_NOTSUPP)
+#define nfserr_toosmall cpu_to_be32(NFSERR_TOOSMALL)
+#define nfserr_serverfault cpu_to_be32(NFSERR_SERVERFAULT)
+#define nfserr_badtype cpu_to_be32(NFSERR_BADTYPE)
+#define nfserr_jukebox cpu_to_be32(NFSERR_JUKEBOX)
+#define nfserr_denied cpu_to_be32(NFSERR_DENIED)
+#define nfserr_deadlock cpu_to_be32(NFSERR_DEADLOCK)
+#define nfserr_expired cpu_to_be32(NFSERR_EXPIRED)
+#define nfserr_bad_cookie cpu_to_be32(NFSERR_BAD_COOKIE)
+#define nfserr_same cpu_to_be32(NFSERR_SAME)
+#define nfserr_clid_inuse cpu_to_be32(NFSERR_CLID_INUSE)
+#define nfserr_stale_clientid cpu_to_be32(NFSERR_STALE_CLIENTID)
+#define nfserr_resource cpu_to_be32(NFSERR_RESOURCE)
+#define nfserr_moved cpu_to_be32(NFSERR_MOVED)
+#define nfserr_nofilehandle cpu_to_be32(NFSERR_NOFILEHANDLE)
+#define nfserr_minor_vers_mismatch cpu_to_be32(NFSERR_MINOR_VERS_MISMATCH)
+#define nfserr_share_denied cpu_to_be32(NFSERR_SHARE_DENIED)
+#define nfserr_stale_stateid cpu_to_be32(NFSERR_STALE_STATEID)
+#define nfserr_old_stateid cpu_to_be32(NFSERR_OLD_STATEID)
+#define nfserr_bad_stateid cpu_to_be32(NFSERR_BAD_STATEID)
+#define nfserr_bad_seqid cpu_to_be32(NFSERR_BAD_SEQID)
+#define nfserr_symlink cpu_to_be32(NFSERR_SYMLINK)
+#define nfserr_not_same cpu_to_be32(NFSERR_NOT_SAME)
+#define nfserr_lock_range cpu_to_be32(NFSERR_LOCK_RANGE)
+#define nfserr_restorefh cpu_to_be32(NFSERR_RESTOREFH)
+#define nfserr_attrnotsupp cpu_to_be32(NFSERR_ATTRNOTSUPP)
+#define nfserr_bad_xdr cpu_to_be32(NFSERR_BAD_XDR)
+#define nfserr_openmode cpu_to_be32(NFSERR_OPENMODE)
+#define nfserr_badowner cpu_to_be32(NFSERR_BADOWNER)
+#define nfserr_locks_held cpu_to_be32(NFSERR_LOCKS_HELD)
+#define nfserr_op_illegal cpu_to_be32(NFSERR_OP_ILLEGAL)
+#define nfserr_grace cpu_to_be32(NFSERR_GRACE)
+#define nfserr_no_grace cpu_to_be32(NFSERR_NO_GRACE)
+#define nfserr_reclaim_bad cpu_to_be32(NFSERR_RECLAIM_BAD)
+#define nfserr_badname cpu_to_be32(NFSERR_BADNAME)
+#define nfserr_admin_revoked cpu_to_be32(NFS4ERR_ADMIN_REVOKED)
+#define nfserr_cb_path_down cpu_to_be32(NFSERR_CB_PATH_DOWN)
+#define nfserr_locked cpu_to_be32(NFSERR_LOCKED)
+#define nfserr_wrongsec cpu_to_be32(NFSERR_WRONGSEC)
+#define nfserr_delay cpu_to_be32(NFS4ERR_DELAY)
+#define nfserr_badiomode cpu_to_be32(NFS4ERR_BADIOMODE)
+#define nfserr_badlayout cpu_to_be32(NFS4ERR_BADLAYOUT)
+#define nfserr_bad_session_digest cpu_to_be32(NFS4ERR_BAD_SESSION_DIGEST)
+#define nfserr_badsession cpu_to_be32(NFS4ERR_BADSESSION)
+#define nfserr_badslot cpu_to_be32(NFS4ERR_BADSLOT)
+#define nfserr_complete_already cpu_to_be32(NFS4ERR_COMPLETE_ALREADY)
+#define nfserr_conn_not_bound_to_session cpu_to_be32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION)
+#define nfserr_deleg_already_wanted cpu_to_be32(NFS4ERR_DELEG_ALREADY_WANTED)
+#define nfserr_back_chan_busy cpu_to_be32(NFS4ERR_BACK_CHAN_BUSY)
+#define nfserr_layouttrylater cpu_to_be32(NFS4ERR_LAYOUTTRYLATER)
+#define nfserr_layoutunavailable cpu_to_be32(NFS4ERR_LAYOUTUNAVAILABLE)
+#define nfserr_nomatching_layout cpu_to_be32(NFS4ERR_NOMATCHING_LAYOUT)
+#define nfserr_recallconflict cpu_to_be32(NFS4ERR_RECALLCONFLICT)
+#define nfserr_unknown_layouttype cpu_to_be32(NFS4ERR_UNKNOWN_LAYOUTTYPE)
+#define nfserr_seq_misordered cpu_to_be32(NFS4ERR_SEQ_MISORDERED)
+#define nfserr_sequence_pos cpu_to_be32(NFS4ERR_SEQUENCE_POS)
+#define nfserr_req_too_big cpu_to_be32(NFS4ERR_REQ_TOO_BIG)
+#define nfserr_rep_too_big cpu_to_be32(NFS4ERR_REP_TOO_BIG)
+#define nfserr_rep_too_big_to_cache cpu_to_be32(NFS4ERR_REP_TOO_BIG_TO_CACHE)
+#define nfserr_retry_uncached_rep cpu_to_be32(NFS4ERR_RETRY_UNCACHED_REP)
+#define nfserr_unsafe_compound cpu_to_be32(NFS4ERR_UNSAFE_COMPOUND)
+#define nfserr_too_many_ops cpu_to_be32(NFS4ERR_TOO_MANY_OPS)
+#define nfserr_op_not_in_session cpu_to_be32(NFS4ERR_OP_NOT_IN_SESSION)
+#define nfserr_hash_alg_unsupp cpu_to_be32(NFS4ERR_HASH_ALG_UNSUPP)
+#define nfserr_clientid_busy cpu_to_be32(NFS4ERR_CLIENTID_BUSY)
+#define nfserr_pnfs_io_hole cpu_to_be32(NFS4ERR_PNFS_IO_HOLE)
+#define nfserr_seq_false_retry cpu_to_be32(NFS4ERR_SEQ_FALSE_RETRY)
+#define nfserr_bad_high_slot cpu_to_be32(NFS4ERR_BAD_HIGH_SLOT)
+#define nfserr_deadsession cpu_to_be32(NFS4ERR_DEADSESSION)
+#define nfserr_encr_alg_unsupp cpu_to_be32(NFS4ERR_ENCR_ALG_UNSUPP)
+#define nfserr_pnfs_no_layout cpu_to_be32(NFS4ERR_PNFS_NO_LAYOUT)
+#define nfserr_not_only_op cpu_to_be32(NFS4ERR_NOT_ONLY_OP)
+#define nfserr_wrong_cred cpu_to_be32(NFS4ERR_WRONG_CRED)
+#define nfserr_wrong_type cpu_to_be32(NFS4ERR_WRONG_TYPE)
+#define nfserr_dirdeleg_unavail cpu_to_be32(NFS4ERR_DIRDELEG_UNAVAIL)
+#define nfserr_reject_deleg cpu_to_be32(NFS4ERR_REJECT_DELEG)
+#define nfserr_returnconflict cpu_to_be32(NFS4ERR_RETURNCONFLICT)
+#define nfserr_deleg_revoked cpu_to_be32(NFS4ERR_DELEG_REVOKED)
+#define nfserr_partner_notsupp cpu_to_be32(NFS4ERR_PARTNER_NOTSUPP)
+#define nfserr_partner_no_auth cpu_to_be32(NFS4ERR_PARTNER_NO_AUTH)
+#define nfserr_union_notsupp cpu_to_be32(NFS4ERR_UNION_NOTSUPP)
+#define nfserr_offload_denied cpu_to_be32(NFS4ERR_OFFLOAD_DENIED)
+#define nfserr_wrong_lfs cpu_to_be32(NFS4ERR_WRONG_LFS)
+#define nfserr_badlabel cpu_to_be32(NFS4ERR_BADLABEL)
+#define nfserr_file_open cpu_to_be32(NFS4ERR_FILE_OPEN)
+#define nfserr_xattr2big cpu_to_be32(NFS4ERR_XATTR2BIG)
+#define nfserr_noxattr cpu_to_be32(NFS4ERR_NOXATTR)
+
+/*
+ * Error codes for internal use. These are based at an impossible
+ * nfsstat4 value so that, once converted to be32, they cannot conflict
+ * with any value defined by the protocol (compare the nlm__int__* codes
+ * in fs/lockd/lockd.h).
+ */
+enum {
+/* end-of-file indicator in readdir */
+ NFSERR_EOF = 30000,
+#define nfserr_eof cpu_to_be32(NFSERR_EOF)
+
+/* replay detected */
+ NFSERR_REPLAY_ME,
+#define nfserr_replay_me cpu_to_be32(NFSERR_REPLAY_ME)
+
+/* nfs41 replay detected */
+ NFSERR_REPLAY_CACHE,
+#define nfserr_replay_cache cpu_to_be32(NFSERR_REPLAY_CACHE)
+
+/* symlink found where dir expected - handled differently to
+ * other symlink found errors by NFSv3.
+ */
+ NFSERR_SYMLINK_NOT_DIR,
+#define nfserr_symlink_not_dir cpu_to_be32(NFSERR_SYMLINK_NOT_DIR)
+};
+
+#endif /* LINUX_NFSD_NFSERR_H */
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index c7c60c35bdfc..b1f3c22af525 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -13,6 +13,7 @@
#include <linux/sunrpc/svcauth_gss.h>
#include <crypto/utils.h>
#include "nfsd.h"
+#include "nfserr.h"
#include "netns.h"
#include "stats.h"
#include "vfs.h"
@@ -334,6 +335,8 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net,
}
switch (fhp->fh_maxsize) {
+ case NFSD_FHSIZE_UNSPEC:
+ break;
case NFS4_FHSIZE:
if (dentry->d_sb->s_export_op->flags & EXPORT_OP_NOATOMIC_ATTR)
fhp->fh_no_atomic_attr = true;
@@ -782,35 +785,54 @@ __be32 fh_getattr(const struct svc_fh *fhp, struct kstat *stat)
AT_STATX_SYNC_AS_STAT));
}
-/**
- * fh_fill_pre_attrs - Fill in pre-op attributes
- * @fhp: file handle to be updated
- *
- */
-__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp)
+static __be32 __must_check __fh_fill_pre_attrs(struct svc_fh *fhp)
{
bool v4 = (fhp->fh_maxsize == NFS4_FHSIZE);
- struct kstat stat;
__be32 err;
if (fhp->fh_no_wcc || fhp->fh_pre_saved)
return nfs_ok;
- err = fh_getattr(fhp, &stat);
+ err = fh_getattr(fhp, &fhp->fh_post_attr);
if (err)
return err;
if (v4)
- fhp->fh_pre_change = nfsd4_change_attribute(&stat);
+ fhp->fh_pre_change = fhp->fh_post_change =
+ nfsd4_change_attribute(&fhp->fh_post_attr);
- fhp->fh_pre_mtime = stat.mtime;
- fhp->fh_pre_ctime = stat.ctime;
- fhp->fh_pre_size = stat.size;
+ fhp->fh_pre_mtime = fhp->fh_post_attr.mtime;
+ fhp->fh_pre_ctime = fhp->fh_post_attr.ctime;
+ fhp->fh_pre_size = fhp->fh_post_attr.size;
fhp->fh_pre_saved = true;
return nfs_ok;
}
/**
+ * fh_fill_pre_attrs - Fill in pre-op attributes
+ * @fhp: file handle to be updated
+ *
+ * Post-op attrs are filled and pre-op attrs are copied
+ * from there. The post-op attrs can later be replaced by
+ * fh_fill_post_attrs() or activated by fh_fill_post_noop().
+ *
+ * The inode must be locked.
+ *
+ * Returns: error from vfs_getattr() which must be checked.
+ */
+__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp)
+{
+ lockdep_assert_held_write(&fhp->fh_dentry->d_inode->i_rwsem);
+ return __fh_fill_pre_attrs(fhp);
+}
+
+__be32 __must_check fh_fill_pre_attrs_unlocked(struct svc_fh *fhp)
+{
+ fhp->fh_no_atomic_attr = true;
+ return __fh_fill_pre_attrs(fhp);
+}
+
+/**
* fh_fill_post_attrs - Fill in post-op attributes
* @fhp: file handle to be updated
*
@@ -826,6 +848,9 @@ __be32 fh_fill_post_attrs(struct svc_fh *fhp)
if (fhp->fh_post_saved)
printk("nfsd: inode locked twice during operation.\n");
+ if (!fhp->fh_no_atomic_attr)
+ lockdep_assert_held_write(&fhp->fh_dentry->d_inode->i_rwsem);
+
err = fh_getattr(fhp, &fhp->fh_post_attr);
if (err)
return err;
@@ -837,29 +862,6 @@ __be32 fh_fill_post_attrs(struct svc_fh *fhp)
return nfs_ok;
}
-/**
- * fh_fill_both_attrs - Fill pre-op and post-op attributes
- * @fhp: file handle to be updated
- *
- * This is used when the directory wasn't changed, but wcc attributes
- * are needed anyway.
- */
-__be32 __must_check fh_fill_both_attrs(struct svc_fh *fhp)
-{
- __be32 err;
-
- err = fh_fill_post_attrs(fhp);
- if (err)
- return err;
-
- fhp->fh_pre_change = fhp->fh_post_change;
- fhp->fh_pre_mtime = fhp->fh_post_attr.mtime;
- fhp->fh_pre_ctime = fhp->fh_post_attr.ctime;
- fhp->fh_pre_size = fhp->fh_post_attr.size;
- fhp->fh_pre_saved = true;
- return nfs_ok;
-}
-
/*
* Release a file handle.
*/
diff --git a/fs/nfsd/nfsfh.h b/fs/nfsd/nfsfh.h
index cdeb5eea65a8..7d8e3f015307 100644
--- a/fs/nfsd/nfsfh.h
+++ b/fs/nfsd/nfsfh.h
@@ -246,6 +246,20 @@ fh_copy_shallow(struct knfsd_fh *dst, const struct knfsd_fh *src)
memcpy(&dst->fh_raw, &src->fh_raw, src->fh_size);
}
+#define NFSD_FHSIZE_UNSPEC 0
+
+/**
+ * fh_init - Prepare a file handle for fh_compose() or fh_verify()
+ * @fhp: File handle to initialize
+ * @maxsize: Largest file handle, in bytes, to build in @fhp
+ *
+ * @maxsize bounds the handle fh_compose() may build: NFS_FHSIZE,
+ * NFS3_FHSIZE, and NFS4_FHSIZE additionally select version-specific
+ * handling in fh_verify(). Callers that only verify an incoming
+ * handle pass NFSD_FHSIZE_UNSPEC, which cannot be composed.
+ *
+ * Return: @fhp
+ */
static __inline__ struct svc_fh *
fh_init(struct svc_fh *fhp, int maxsize)
{
@@ -337,6 +351,18 @@ static inline void fh_clear_pre_post_attrs(struct svc_fh *fhp)
u64 nfsd4_change_attribute(const struct kstat *stat);
__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp);
+__be32 __must_check fh_fill_pre_attrs_unlocked(struct svc_fh *fhp);
__be32 fh_fill_post_attrs(struct svc_fh *fhp);
-__be32 __must_check fh_fill_both_attrs(struct svc_fh *fhp);
+
+/**
+ * fh_fill_post_noop - Copy pre attrs to post attrs
+ * @fhp: file handle to be updated
+ *
+ * This is used when the directory wasn't changed, but wcc attributes
+ * are needed anyway.
+ */
+static inline void fh_fill_post_noop(struct svc_fh *fhp)
+{
+ fhp->fh_post_saved = true;
+}
#endif /* _LINUX_NFSD_NFSFH_H */
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index e2b5f8a241be..48541ef7644f 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -10,6 +10,7 @@
#include "cache.h"
#include "xdr.h"
#include "vfs.h"
+#include "nfserr.h"
#include "trace.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
@@ -265,7 +266,7 @@ nfsd_proc_write(struct svc_rqst *rqstp)
fh_copy(&resp->fh, &argp->fh);
resp->status = nfsd_write(rqstp, &resp->fh, argp->offset,
- &argp->payload, &cnt, NFS_DATA_SYNC, NULL);
+ &argp->payload, &cnt, IOCB_DSYNC, NULL);
if (resp->status == nfs_ok)
resp->status = fh_getattr(&resp->fh, &resp->stat);
else if (resp->status == nfserr_jukebox)
@@ -291,6 +292,7 @@ nfsd_proc_create(struct svc_rqst *rqstp)
struct nfsd_attrs attrs = {
.na_iattr = attr,
};
+ struct svc_export *exp;
struct inode *inode;
struct dentry *dchild;
int type, mode;
@@ -319,8 +321,22 @@ nfsd_proc_create(struct svc_rqst *rqstp)
resp->status = nfserrno(PTR_ERR(dchild));
goto out_write;
}
+ /*
+ * If name exists we need to check for mountpoints
+ */
+ exp = exp_get(dirfhp->fh_export);
+ if (d_is_reg(dchild) &&
+ unlikely(nfsd_mountpoint(dchild, exp))) {
+ resp->status = nfsd_cross_mnt(rqstp, &dchild, &exp);
+ if (resp->status != nfs_ok) {
+ exp_put(exp);
+ goto out_unlock;
+ }
+ }
+
fh_init(newfhp, NFS_FHSIZE);
- resp->status = fh_compose(newfhp, dirfhp->fh_export, dchild, dirfhp);
+ resp->status = fh_compose(newfhp, exp, dchild, dirfhp);
+ exp_put(exp);
if (!resp->status && d_really_is_negative(dchild))
resp->status = nfserr_noent;
if (resp->status) {
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index 2edf716ea022..c04ef9d180ce 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -25,7 +25,10 @@
#include <net/addrconf.h>
#include <net/ipv6.h>
#include <net/net_namespace.h>
+
#include "nfsd.h"
+#include "nfserr.h"
+#include "nfs4ctl.h"
#include "cache.h"
#include "vfs.h"
#include "netns.h"
@@ -204,6 +207,11 @@ int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change
return 0;
}
+bool nfsd_v4client(struct svc_rqst *rqstp)
+{
+ return rqstp && rqstp->rq_prog == NFS_PROGRAM && rqstp->rq_vers == 4;
+}
+
bool nfsd_net_try_get(struct net *net) __must_hold(rcu)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 019f0cc971a7..0961c13d6ab1 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -8,6 +8,7 @@
#include <linux/filelock.h>
#include "vfs.h"
+#include "nfserr.h"
#include "xdr.h"
#include "auth.h"
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 2d00a411c663..c65b604e29f1 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -45,6 +45,25 @@
#include "nfsfh.h"
#include "nfsd.h"
+/*
+ * Before processing a COMPOUND operation, we have to check that there
+ * is enough space in the buffer for XDR encode to succeed. otherwise,
+ * we might process an operation with side effects, and be unable to
+ * tell the client that the operation succeeded.
+ *
+ * COMPOUND_ERR_SLACK_SPACE - this is the minimum bytes of buffer space
+ * needed to encode an operation which has failed with NFS4ERR_RESOURCE.
+ * care is taken to ensure that we never fall below this level for any
+ * reason.
+ */
+#define COMPOUND_ERR_SLACK_SPACE 16 /* OP_SETATTR */
+
+#define NFSD_LAUNDROMAT_MINTIMEOUT 1 /* seconds */
+#define NFSD_CLIENT_MAX_TRIM_PER_RUN 128
+#define NFS4_CLIENTS_PER_GB 1024
+#define NFSD_DELEGRETURN_TIMEOUT (HZ / 34) /* 30ms */
+#define NFSD_CB_GETATTR_TIMEOUT NFSD_DELEGRETURN_TIMEOUT
+
typedef struct {
u32 cl_boot;
u32 cl_id;
@@ -126,10 +145,13 @@ struct nfs4_stid {
#define SC_TYPE_COPY BIT(4)
unsigned short sc_type;
-/* nn->deleg_lock protects sc_status for delegation stateids.
- * ->cl_lock protects sc_status for open and lock stateids.
- * ->st_mutex also protect sc_status for open stateids.
- * ->ls_lock protects sc_status for layout stateids.
+/*
+ * nn->deleg_lock protects sc_status for hashed delegation stateids.
+ * ->cl_lock protects the bits set as one is disposed of
+ * (SC_STATUS_CLOSED, SC_STATUS_FREEABLE, SC_STATUS_FREED) and
+ * sc_status for open and lock stateids. ->st_mutex also protects
+ * sc_status for open stateids. ->ls_lock protects sc_status for
+ * layout stateids.
*/
/*
* For an open stateid kept around *only* to process close replays.
@@ -249,6 +271,7 @@ struct nfsd4_cb_notify {
struct nfsd_notify_event *ncn_evt[NOTIFY4_EVENT_QUEUE_SIZE]; // list of events
struct page *ncn_pages[NOTIFY4_PAGE_ARRAY_SIZE]; // for encoding
struct notify4 *ncn_nf; // array of notify4's to be sent
+ u32 *ncn_masks; // host-order notify_mask backing for ncn_nf[]
bool ncn_encode_err; // did encoding fail?
struct nfsd4_callback ncn_cb; // notify4 callback
};
@@ -270,7 +293,8 @@ struct nfsd4_cb_notify {
* If the server attempts to recall a delegation and the client doesn't do so
* before a timeout, the server may also revoke the delegation. In that case,
* the object will either be destroyed (v4.0) or moved to a per-client list of
- * revoked delegations (v4.1+).
+ * revoked delegations (v4.1+). A v4.1+ client that rejects the recall holds
+ * no record of the delegation, so the object is destroyed rather than listed.
*
* This object is a superset of the nfs4_stid.
*/
@@ -286,9 +310,19 @@ struct nfs4_delegation {
int dl_retries;
struct nfsd4_callback dl_recall;
bool dl_recalled;
+ bool dl_recall_rejected;
bool dl_written;
bool dl_setattr;
+ /* Forward-channel slot that carried the granting request */
+ struct {
+ u32 sessionid_seq;
+ u32 slotid;
+ u32 seqid;
+ bool valid;
+ bool retired_at_send;
+ } dl_recall_grant;
+
union {
/* for CB_GETATTR */
struct nfs4_cb_fattr dl_cb_fattr;
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 8923a9910a08..f9131827d391 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -34,15 +34,14 @@
#include <linux/sunrpc/xdr.h>
#include <linux/fileattr.h>
-#include "xdr3.h"
-
#ifdef CONFIG_NFSD_V4
#include "acl.h"
#include "idmap.h"
-#include "xdr4.h"
#endif /* CONFIG_NFSD_V4 */
#include "nfsd.h"
+#include "nfserr.h"
+#include "nfs4ctl.h"
#include "netns.h"
#include "stats.h"
#include "vfs.h"
@@ -63,7 +62,7 @@ u64 nfsd_io_cache_write __read_mostly = NFSD_IO_BUFFERED;
* it's an error we don't expect, log it once and return nfserr_io.
*/
__be32
-nfserrno (int errno)
+nfserrno(int errno)
{
static struct {
__be32 nfserr;
@@ -107,6 +106,8 @@ nfserrno (int errno)
{ nfserr_perm, -ENOKEY },
{ nfserr_no_grace, -ENOGRACE},
{ nfserr_io, -EBADMSG },
+ { nfserr_symlink, -ELOOP },
+ { nfserr_wrong_type, -EFTYPE },
};
int i;
@@ -118,15 +119,15 @@ nfserrno (int errno)
return nfserr_io;
}
-/*
- * Called from nfsd_lookup and encode_dirent. Check if we have crossed
+/*
+ * Called from nfsd_lookup and encode_dirent. Check if we have crossed
* a mount point.
- * Returns -EAGAIN or -ETIMEDOUT leaving *dpp and *expp unchanged,
+ * Returns an nfs error leaving *dpp and *expp unchanged,
* or nfs_ok having possibly changed *dpp and *expp
*/
-int
-nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
- struct svc_export **expp)
+__be32
+nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
+ struct svc_export **expp)
{
struct svc_export *exp = *expp, *exp2 = NULL;
struct dentry *dentry = *dpp;
@@ -134,6 +135,7 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
.dentry = dget(dentry)};
unsigned int follow_flags = 0;
int err = 0;
+ __be32 nfserr = nfs_ok;
if (exp->ex_flags & NFSEXP_CROSSMOUNT)
follow_flags = LOOKUP_AUTOMOUNT;
@@ -163,23 +165,28 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
err = 0;
} else if (nfsd_v4client(rqstp) ||
(exp->ex_flags & NFSEXP_CROSSMOUNT) || EX_NOHIDE(exp2)) {
- /* successfully crossed mount point */
- /*
- * This is subtle: path.dentry is *not* on path.mnt
- * at this point. The only reason we are safe is that
- * original mnt is pinned down by exp, so we should
- * put path *before* putting exp
- */
- *dpp = path.dentry;
- path.dentry = dentry;
- *expp = exp2;
- exp2 = exp;
+ nfserr = check_nfsd_access(exp, rqstp);
+ if (nfserr == nfs_ok) {
+ /* successfully crossed mount point */
+ /*
+ * This is subtle: path.dentry is *not* on path.mnt
+ * at this point. The only reason we are safe is that
+ * original mnt is pinned down by exp, so we should
+ * put path *before* putting exp
+ */
+ *dpp = path.dentry;
+ path.dentry = dentry;
+ *expp = exp2;
+ exp2 = exp;
+ }
}
out:
path_put(&path);
if (exp2)
exp_put(exp2);
- return err;
+ if (nfserr)
+ return nfserr;
+ return nfserrno(err);
}
static void follow_to_parent(struct path *path)
@@ -277,10 +284,12 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (IS_ERR(dentry))
goto out_nfserr;
if (nfsd_mountpoint(dentry, exp)) {
- host_err = nfsd_cross_mnt(rqstp, &dentry, &exp);
- if (host_err) {
+ __be32 nfserr = nfsd_cross_mnt(rqstp, &dentry, &exp);
+
+ if (nfserr) {
dput(dentry);
- goto out_nfserr;
+ exp_put(exp);
+ return nfserr;
}
}
}
@@ -327,9 +336,6 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
err = nfsd_lookup_dentry(rqstp, fhp, name, len, &exp, &dentry);
if (err)
return err;
- err = check_nfsd_access(exp, rqstp, false);
- if (err)
- goto out;
/*
* Note: we compose the file handle now, but as the
* dentry may be negative, it may need to be updated.
@@ -337,15 +343,26 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
err = fh_compose(resfh, exp, dentry, fhp);
if (!err && d_really_is_negative(dentry))
err = nfserr_noent;
-out:
+
dput(dentry);
exp_put(exp);
return err;
}
-static void
-commit_reset_write_verifier(struct nfsd_net *nn, struct svc_rqst *rqstp,
- int err)
+/**
+ * nfsd_maybe_reset_write_verifier - Reset the write verifier after an I/O error
+ * @nn: nfsd namespace holding the write verifier
+ * @rqstp: RPC transaction context
+ * @err: errno reported by the failed operation
+ *
+ * A write verifier reset tells clients that unstable data the server has
+ * already acknowledged might have been lost. Client response is to resend
+ * in-flight dirty data.
+ *
+ * Context: Process context.
+ */
+void nfsd_maybe_reset_write_verifier(struct nfsd_net *nn,
+ struct svc_rqst *rqstp, int err)
{
switch (err) {
case -EAGAIN:
@@ -682,56 +699,67 @@ int nfsd4_is_junction(struct dentry *dentry)
return 1;
}
-static struct nfsd4_compound_state *nfsd4_get_cstate(struct svc_rqst *rqstp)
+/**
+ * nfsd_clone_file_range - Clone a range of one file into another
+ * @src: file the range is cloned from
+ * @src_pos: offset in @src where the source range begins
+ * @dst: file the range is cloned into
+ * @dst_pos: offset in @dst where the destination range begins
+ * @count: length of the range, or zero to clone through end-of-file
+ * @since: receives @dst's writeback error state, sampled before the clone
+ *
+ * A caller that has to place the cloned data on durable storage passes
+ * @since to nfsd_clone_sync_range() once this call succeeds. Sampling
+ * happens here because a writeback error raised by the clone's own
+ * dirty pages has to fall inside the sampled interval.
+ *
+ * Context: Process context.
+ * Return: zero on success, or a negative errno
+ */
+int nfsd_clone_file_range(struct file *src, u64 src_pos, struct file *dst,
+ u64 dst_pos, u64 count, errseq_t *since)
{
- return &((struct nfsd4_compoundres *)rqstp->rq_resp)->cstate;
+ loff_t cloned;
+
+ *since = READ_ONCE(dst->f_wb_err);
+ cloned = vfs_clone_file_range(src, src_pos, dst, dst_pos, count, 0);
+ if (cloned < 0)
+ return cloned;
+ if (count && cloned != count)
+ return -EINVAL;
+ return 0;
}
-__be32 nfsd4_clone_file_range(struct svc_rqst *rqstp,
- struct nfsd_file *nf_src, u64 src_pos,
- struct nfsd_file *nf_dst, u64 dst_pos,
- u64 count, bool sync)
+/**
+ * nfsd_clone_sync_range - Commit a cloned range to durable storage
+ * @src: file the range was cloned from, whose metadata is committed too
+ * @dst: file the range was cloned into
+ * @dst_pos: offset in @dst where the cloned range begins
+ * @count: length of the range, or zero if the clone ran to end-of-file
+ * @since: @dst's writeback error state as sampled by
+ * nfsd_clone_file_range()
+ *
+ * Context: Process context.
+ * Return: zero on success, or a negative errno
+ */
+int nfsd_clone_sync_range(struct file *src, struct file *dst, u64 dst_pos,
+ u64 count, errseq_t since)
{
- struct file *src = nf_src->nf_file;
- struct file *dst = nf_dst->nf_file;
- errseq_t since;
- loff_t cloned;
- __be32 ret = 0;
+ loff_t dst_end = count ? dst_pos + count - 1 : LLONG_MAX;
+ int status;
- since = READ_ONCE(dst->f_wb_err);
- cloned = vfs_clone_file_range(src, src_pos, dst, dst_pos, count, 0);
- if (cloned < 0) {
- ret = nfserrno(cloned);
- goto out_err;
- }
- if (count && cloned != count) {
- ret = nfserrno(-EINVAL);
- goto out_err;
- }
- if (sync) {
- loff_t dst_end = count ? dst_pos + count - 1 : LLONG_MAX;
- int status = vfs_fsync_range(dst, dst_pos, dst_end, 0);
-
- if (!status)
- status = filemap_check_wb_err(dst->f_mapping, since);
- if (!status)
- status = commit_inode_metadata(file_inode(src));
- if (status < 0) {
- struct nfsd_net *nn = net_generic(nf_dst->nf_net,
- nfsd_net_id);
-
- trace_nfsd_clone_file_range_err(rqstp,
- &nfsd4_get_cstate(rqstp)->save_fh,
- src_pos,
- &nfsd4_get_cstate(rqstp)->current_fh,
- dst_pos,
- count, status);
- commit_reset_write_verifier(nn, rqstp, status);
- ret = nfserrno(status);
- }
+ status = vfs_fsync_range(dst, dst_pos, dst_end, 0);
+ if (!status)
+ status = filemap_check_wb_err(dst->f_mapping, since);
+ if (!status) {
+ /*
+ * A reflink marks extents shared in the source inode too,
+ * so the source's metadata has to reach durable storage
+ * even though its data is untouched.
+ */
+ status = commit_inode_metadata(file_inode(src));
}
-out_err:
- return ret;
+ return status;
}
ssize_t nfsd_copy_file_range(struct file *src, u64 src_pos, struct file *dst,
@@ -773,64 +801,21 @@ __be32 nfsd4_vfs_fallocate(struct svc_rqst *rqstp, struct svc_fh *fhp,
}
#endif /* defined(CONFIG_NFSD_V4) */
-/*
- * Check server access rights to a file system object
+/**
+ * nfsd_access - Check caller's access rights to a file system object
+ * @rqstp: RPC transaction context
+ * @fhp: target NFS filehandle
+ * @maps: tables mapping on-the-wire access bits to NFSD_MAY flags
+ * @access: requested access bits on entry, permitted bits on return
+ * @supported: optional output of the access bits the server supports
+ *
+ * Return: nfs_ok on success, otherwise an nfserr status code
*/
-struct accessmap {
- u32 access;
- int how;
-};
-static struct accessmap nfs3_regaccess[] = {
- { NFS3_ACCESS_READ, NFSD_MAY_READ },
- { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC },
- { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
- { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE },
-
-#ifdef CONFIG_NFSD_V4
- { NFS4_ACCESS_XAREAD, NFSD_MAY_READ },
- { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE },
- { NFS4_ACCESS_XALIST, NFSD_MAY_READ },
-#endif
-
- { 0, 0 }
-};
-
-static struct accessmap nfs3_diraccess[] = {
- { NFS3_ACCESS_READ, NFSD_MAY_READ },
- { NFS3_ACCESS_LOOKUP, NFSD_MAY_EXEC },
- { NFS3_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC},
- { NFS3_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE },
- { NFS3_ACCESS_DELETE, NFSD_MAY_REMOVE },
-
-#ifdef CONFIG_NFSD_V4
- { NFS4_ACCESS_XAREAD, NFSD_MAY_READ },
- { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE },
- { NFS4_ACCESS_XALIST, NFSD_MAY_READ },
-#endif
-
- { 0, 0 }
-};
-
-static struct accessmap nfs3_anyaccess[] = {
- /* Some clients - Solaris 2.6 at least, make an access call
- * to the server to check for access for things like /dev/null
- * (which really, the server doesn't care about). So
- * We provide simple access checking for them, looking
- * mainly at mode bits, and we make sure to ignore read-only
- * filesystem checks
- */
- { NFS3_ACCESS_READ, NFSD_MAY_READ },
- { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC },
- { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
- { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS },
-
- { 0, 0 }
-};
-
-__be32
-nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *supported)
+__be32 nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp,
+ const struct nfsd_access_maps *maps,
+ u32 *access, u32 *supported)
{
- struct accessmap *map;
+ const struct nfsd_access_map *map;
struct svc_export *export;
struct dentry *dentry;
u32 query, result = 0, sresult = 0;
@@ -844,12 +829,11 @@ nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *suppor
dentry = fhp->fh_dentry;
if (d_is_reg(dentry))
- map = nfs3_regaccess;
+ map = maps->regular;
else if (d_is_dir(dentry))
- map = nfs3_diraccess;
+ map = maps->directory;
else
- map = nfs3_anyaccess;
-
+ map = maps->other;
query = *access;
for (; map->access; map++) {
@@ -859,7 +843,7 @@ nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *suppor
sresult |= map->access;
err2 = nfsd_permission(&rqstp->rq_cred, export,
- dentry, map->how);
+ dentry, map->may);
switch (err2) {
case nfs_ok:
result |= map->access;
@@ -1424,7 +1408,7 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
* @offset: Byte offset of start
* @payload: xdr_buf containing the write payload
* @cnt: IN: number of bytes to write, OUT: number of bytes actually written
- * @stable: An NFS stable_how value
+ * @iocb_flags: VFS IOCB_* flags expressing the requested write stability
* @verf: NFS WRITE verifier
*
* Upon return, caller must invoke fh_put on @fhp.
@@ -1436,7 +1420,7 @@ __be32
nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file *nf, loff_t offset,
const struct xdr_buf *payload, unsigned long *cnt,
- int stable, __be32 *verf)
+ int iocb_flags, __be32 *verf)
{
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
struct file *file = nf->nf_file;
@@ -1473,21 +1457,11 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
exp = fhp->fh_export;
if (!EX_ISSYNC(exp))
- stable = NFS_UNSTABLE;
+ iocb_flags = 0;
init_sync_kiocb(&kiocb, file);
kiocb.ki_pos = offset;
- if (likely(!fhp->fh_use_wgather)) {
- switch (stable) {
- case NFS_FILE_SYNC:
- /* persist data and timestamps */
- kiocb.ki_flags |= IOCB_DSYNC | IOCB_SYNC;
- break;
- case NFS_DATA_SYNC:
- /* persist data only */
- kiocb.ki_flags |= IOCB_DSYNC;
- break;
- }
- }
+ if (likely(!fhp->fh_use_wgather))
+ kiocb.ki_flags |= iocb_flags;
nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload);
if (nvecs < 0) {
@@ -1517,21 +1491,21 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
break;
}
if (host_err < 0) {
- commit_reset_write_verifier(nn, rqstp, host_err);
+ nfsd_maybe_reset_write_verifier(nn, rqstp, host_err);
goto out_nfserr;
}
nfsd_stats_io_write_add(nn, exp, *cnt);
fsnotify_modify(file);
host_err = filemap_check_wb_err(file->f_mapping, since);
if (host_err < 0) {
- commit_reset_write_verifier(nn, rqstp, host_err);
+ nfsd_maybe_reset_write_verifier(nn, rqstp, host_err);
goto out_nfserr;
}
- if (stable && fhp->fh_use_wgather) {
+ if (iocb_flags && fhp->fh_use_wgather) {
host_err = wait_for_concurrent_writes(file);
if (host_err < 0)
- commit_reset_write_verifier(nn, rqstp, host_err);
+ nfsd_maybe_reset_write_verifier(nn, rqstp, host_err);
}
out_nfserr:
@@ -1619,7 +1593,7 @@ __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
* @offset: Byte offset of start
* @payload: xdr_buf containing the write payload
* @cnt: IN: number of bytes to write, OUT: number of bytes actually written
- * @stable: An NFS stable_how value
+ * @iocb_flags: VFS IOCB_* flags expressing the requested write stability
* @verf: NFS WRITE verifier
*
* Upon return, caller must invoke fh_put on @fhp.
@@ -1629,8 +1603,8 @@ __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
*/
__be32
nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
- const struct xdr_buf *payload, unsigned long *cnt, int stable,
- __be32 *verf)
+ const struct xdr_buf *payload, unsigned long *cnt,
+ int iocb_flags, __be32 *verf)
{
struct nfsd_file *nf;
__be32 err;
@@ -1642,7 +1616,7 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
goto out;
err = nfsd_vfs_write(rqstp, fhp, nf, offset, payload, cnt,
- stable, verf);
+ iocb_flags, verf);
nfsd_file_put(nf);
out:
trace_nfsd_write_done(rqstp, fhp, offset, *cnt);
@@ -1707,14 +1681,14 @@ nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_file *nf,
err2 = filemap_check_wb_err(nf->nf_file->f_mapping,
since);
if (err2 < 0)
- commit_reset_write_verifier(nn, rqstp, err2);
+ nfsd_maybe_reset_write_verifier(nn, rqstp, err2);
err = nfserrno(err2);
break;
case -EINVAL:
err = nfserr_notsupp;
break;
default:
- commit_reset_write_verifier(nn, rqstp, err2);
+ nfsd_maybe_reset_write_verifier(nn, rqstp, err2);
err = nfserrno(err2);
}
} else
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index 4af2ff9e9dfe..f0cb184643f2 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -37,6 +37,17 @@
#define NFSD_MAY_CREATE (NFSD_MAY_EXEC|NFSD_MAY_WRITE)
#define NFSD_MAY_REMOVE (NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
+struct nfsd_access_map {
+ u32 access;
+ int may;
+};
+
+struct nfsd_access_maps {
+ const struct nfsd_access_map *regular;
+ const struct nfsd_access_map *directory;
+ const struct nfsd_access_map *other;
+};
+
struct nfsd_file;
/*
@@ -75,9 +86,14 @@ static inline bool nfsd_attrs_valid(struct nfsd_attrs *attrs)
attrs->na_pacl || attrs->na_dpacl);
}
+struct nfsd_net;
+
__be32 nfserrno (int errno);
-int nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
- struct svc_export **expp);
+void nfsd_maybe_reset_write_verifier(struct nfsd_net *nn,
+ struct svc_rqst *rqstp,
+ int err);
+__be32 nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
+ struct svc_export **expp);
__be32 nfsd_lookup(struct svc_rqst *, struct svc_fh *,
const char *, unsigned int, struct svc_fh *);
__be32 nfsd_lookup_dentry(struct svc_rqst *, struct svc_fh *,
@@ -89,10 +105,11 @@ int nfsd_mountpoint(struct dentry *, struct svc_export *);
#ifdef CONFIG_NFSD_V4
__be32 nfsd4_vfs_fallocate(struct svc_rqst *, struct svc_fh *,
struct file *, loff_t, loff_t, int);
-__be32 nfsd4_clone_file_range(struct svc_rqst *rqstp,
- struct nfsd_file *nf_src, u64 src_pos,
- struct nfsd_file *nf_dst, u64 dst_pos,
- u64 count, bool sync);
+int nfsd_clone_file_range(struct file *src, u64 src_pos,
+ struct file *dst, u64 dst_pos,
+ u64 count, errseq_t *since);
+int nfsd_clone_sync_range(struct file *src, struct file *dst,
+ u64 dst_pos, u64 count, errseq_t since);
#endif /* CONFIG_NFSD_V4 */
__be32 nfsd_create_locked(struct svc_rqst *, struct svc_fh *,
struct nfsd_attrs *attrs, int type, dev_t rdev,
@@ -100,7 +117,9 @@ __be32 nfsd_create_locked(struct svc_rqst *, struct svc_fh *,
__be32 nfsd_create(struct svc_rqst *, struct svc_fh *,
char *name, int len, struct nfsd_attrs *attrs,
int type, dev_t rdev, struct svc_fh *res);
-__be32 nfsd_access(struct svc_rqst *, struct svc_fh *, u32 *, u32 *);
+__be32 nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp,
+ const struct nfsd_access_maps *maps,
+ u32 *access, u32 *supported);
__be32 nfsd_create_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct svc_fh *resfhp, struct nfsd_attrs *iap);
__be32 nfsd_commit(struct svc_rqst *rqst, struct svc_fh *fhp,
@@ -135,11 +154,13 @@ __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
u32 *eof);
__be32 nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
loff_t offset, const struct xdr_buf *payload,
- unsigned long *cnt, int stable, __be32 *verf);
+ unsigned long *cnt, int iocb_flags,
+ __be32 *verf);
__be32 nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file *nf, loff_t offset,
const struct xdr_buf *payload,
- unsigned long *cnt, int stable, __be32 *verf);
+ unsigned long *cnt, int iocb_flags,
+ __be32 *verf);
__be32 nfsd_readlink(struct svc_rqst *, struct svc_fh *,
char *, int *);
__be32 nfsd_symlink(struct svc_rqst *, struct svc_fh *,
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 344203874b4c..cad875d14231 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -39,7 +39,7 @@ struct nfsd3_writeargs {
svc_fh fh;
__u64 offset;
__u32 count;
- int stable;
+ __u32 stable;
__u32 len;
struct xdr_buf payload;
};
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index c7eda5bc833b..7bbb375874ef 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -50,134 +50,6 @@
#define HAS_CSTATE_FLAG(c, f) ((c)->sid_flags & (f))
#define CLEAR_CSTATE_FLAG(c, f) ((c)->sid_flags &= ~(f))
-/**
- * nfsd4_encode_bool - Encode an XDR bool type result
- * @xdr: target XDR stream
- * @val: boolean value to encode
- *
- * Return values:
- * %nfs_ok: @val encoded; @xdr advanced to next position
- * %nfserr_resource: stream buffer space exhausted
- */
-static __always_inline __be32
-nfsd4_encode_bool(struct xdr_stream *xdr, bool val)
-{
- __be32 *p = xdr_reserve_space(xdr, XDR_UNIT);
-
- if (unlikely(p == NULL))
- return nfserr_resource;
- *p = val ? xdr_one : xdr_zero;
- return nfs_ok;
-}
-
-/**
- * nfsd4_encode_uint32_t - Encode an XDR uint32_t type result
- * @xdr: target XDR stream
- * @val: integer value to encode
- *
- * Return values:
- * %nfs_ok: @val encoded; @xdr advanced to next position
- * %nfserr_resource: stream buffer space exhausted
- */
-static __always_inline __be32
-nfsd4_encode_uint32_t(struct xdr_stream *xdr, u32 val)
-{
- __be32 *p = xdr_reserve_space(xdr, XDR_UNIT);
-
- if (unlikely(p == NULL))
- return nfserr_resource;
- *p = cpu_to_be32(val);
- return nfs_ok;
-}
-
-#define nfsd4_encode_aceflag4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_acemask4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_acetype4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_count4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_mode4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_nfs_lease4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_qop4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_sequenceid4(x, v) nfsd4_encode_uint32_t(x, v)
-#define nfsd4_encode_slotid4(x, v) nfsd4_encode_uint32_t(x, v)
-
-/**
- * nfsd4_encode_uint64_t - Encode an XDR uint64_t type result
- * @xdr: target XDR stream
- * @val: integer value to encode
- *
- * Return values:
- * %nfs_ok: @val encoded; @xdr advanced to next position
- * %nfserr_resource: stream buffer space exhausted
- */
-static __always_inline __be32
-nfsd4_encode_uint64_t(struct xdr_stream *xdr, u64 val)
-{
- __be32 *p = xdr_reserve_space(xdr, XDR_UNIT * 2);
-
- if (unlikely(p == NULL))
- return nfserr_resource;
- put_unaligned_be64(val, p);
- return nfs_ok;
-}
-
-#define nfsd4_encode_changeid4(x, v) nfsd4_encode_uint64_t(x, v)
-#define nfsd4_encode_nfs_cookie4(x, v) nfsd4_encode_uint64_t(x, v)
-#define nfsd4_encode_length4(x, v) nfsd4_encode_uint64_t(x, v)
-#define nfsd4_encode_offset4(x, v) nfsd4_encode_uint64_t(x, v)
-
-/**
- * nfsd4_encode_opaque_fixed - Encode a fixed-length XDR opaque type result
- * @xdr: target XDR stream
- * @data: pointer to data
- * @size: length of data in bytes
- *
- * Return values:
- * %nfs_ok: @data encoded; @xdr advanced to next position
- * %nfserr_resource: stream buffer space exhausted
- */
-static __always_inline __be32
-nfsd4_encode_opaque_fixed(struct xdr_stream *xdr, const void *data,
- size_t size)
-{
- __be32 *p = xdr_reserve_space(xdr, xdr_align_size(size));
- size_t pad = xdr_pad_size(size);
-
- if (unlikely(p == NULL))
- return nfserr_resource;
- memcpy(p, data, size);
- if (pad)
- memset((char *)p + size, 0, pad);
- return nfs_ok;
-}
-
-/**
- * nfsd4_encode_opaque - Encode a variable-length XDR opaque type result
- * @xdr: target XDR stream
- * @data: pointer to data
- * @size: length of data in bytes
- *
- * Return values:
- * %nfs_ok: @data encoded; @xdr advanced to next position
- * %nfserr_resource: stream buffer space exhausted
- */
-static __always_inline __be32
-nfsd4_encode_opaque(struct xdr_stream *xdr, const void *data, size_t size)
-{
- size_t pad = xdr_pad_size(size);
- __be32 *p;
-
- p = xdr_reserve_space(xdr, XDR_UNIT + xdr_align_size(size));
- if (unlikely(p == NULL))
- return nfserr_resource;
- *p++ = cpu_to_be32(size);
- memcpy(p, data, size);
- if (pad)
- memset((char *)p + size, 0, pad);
- return nfs_ok;
-}
-
-#define nfsd4_encode_component4(x, d, s) nfsd4_encode_opaque(x, d, s)
-
struct nfsd4_compound_state {
struct svc_fh current_fh;
struct svc_fh save_fh;
@@ -642,17 +514,6 @@ svcxdr_decode_deviceid4(__be32 *p, struct nfsd4_deviceid *devid)
return p;
}
-static inline __be32
-nfsd4_decode_deviceid4(struct xdr_stream *xdr, struct nfsd4_deviceid *devid)
-{
- __be32 *p = xdr_inline_decode(xdr, NFS4_DEVICEID4_SIZE);
-
- if (unlikely(!p))
- return nfserr_bad_xdr;
- svcxdr_decode_deviceid4(p, devid);
- return nfs_ok;
-}
-
struct nfsd4_layout_seg {
u32 iomode;
u64 offset;
@@ -736,6 +597,19 @@ struct nfsd4_cb_offload {
u32 co_referring_seqno;
};
+struct nfsd4_ssc_umount_item {
+ struct list_head nsui_list;
+ bool nsui_busy;
+ /*
+ * nsui_refcnt inited to 2, 1 on list and 1 for consumer. Entry
+ * is removed when refcnt drops to 1 and nsui_expire expires.
+ */
+ refcount_t nsui_refcnt;
+ unsigned long nsui_expire;
+ struct vfsmount *nsui_vfsmount;
+ char nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1];
+};
+
struct nfsd4_copy {
/* request */
stateid_t cp_src_stateid;
diff --git a/fs/nfsd/xdr4cb.h b/fs/nfsd/xdr4cb.h
index b06d0170d7c4..838f8629821f 100644
--- a/fs/nfsd/xdr4cb.h
+++ b/fs/nfsd/xdr4cb.h
@@ -6,29 +6,30 @@
#define cb_compound_enc_hdr_sz 4
#define cb_compound_dec_hdr_sz (3 + (NFS4_MAXTAGLEN >> 2))
#define sessionid_sz (NFS4_MAX_SESSIONID_LEN >> 2)
+#define op_enc_sz 1
#define enc_referring_call4_sz (1 + 1)
#define enc_referring_call_list4_sz (sessionid_sz + 1 + \
enc_referring_call4_sz)
-#define cb_sequence_enc_sz (sessionid_sz + 4 + \
- enc_referring_call_list4_sz)
+#define cb_sequence_enc_sz (op_enc_sz + sessionid_sz + 4 + \
+ 1 + enc_referring_call_list4_sz)
#define cb_sequence_dec_sz (op_dec_sz + sessionid_sz + 4)
-#define op_enc_sz 1
#define op_dec_sz 2
#define enc_nfs4_fh_sz (1 + (NFS4_FHSIZE >> 2))
#define enc_stateid_sz (NFS4_STATEID_SIZE >> 2)
#define NFS4_enc_cb_recall_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
- 1 + enc_stateid_sz + \
- enc_nfs4_fh_sz)
+ op_enc_sz + enc_stateid_sz + \
+ 1 + enc_nfs4_fh_sz)
#define NFS4_dec_cb_recall_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
#define NFS4_enc_cb_layout_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
- 1 + 3 + \
- enc_nfs4_fh_sz + 4)
+ op_enc_sz + 3 + 1 + \
+ enc_nfs4_fh_sz + 4 + \
+ enc_stateid_sz)
#define NFS4_dec_cb_layout_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
@@ -47,7 +48,7 @@
#define NFS4_enc_cb_notify_lock_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
- 2 + 1 + \
+ op_enc_sz + 2 + 1 + \
XDR_QUADLEN(NFS4_OPAQUE_LIMIT) + \
enc_nfs4_fh_sz)
#define NFS4_dec_cb_notify_lock_sz (cb_compound_dec_hdr_sz + \
@@ -57,6 +58,7 @@
XDR_QUADLEN(NFS4_VERIFIER_SIZE))
#define NFS4_enc_cb_offload_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
+ op_enc_sz + \
enc_nfs4_fh_sz + \
enc_stateid_sz + \
enc_cb_offload_info_sz)
@@ -65,7 +67,7 @@
op_dec_sz)
#define NFS4_enc_cb_recall_any_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
- 1 + 1 + 1)
+ op_enc_sz + 1 + 1 + 1)
#define NFS4_dec_cb_recall_any_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
diff --git a/include/linux/nfs.h b/include/linux/nfs.h
index 0906a0b40c6a..8c2818db43c5 100644
--- a/include/linux/nfs.h
+++ b/include/linux/nfs.h
@@ -11,59 +11,8 @@
#include <linux/cred.h>
#include <linux/sunrpc/auth.h>
#include <linux/sunrpc/msg_prot.h>
-#include <linux/string.h>
-#include <linux/crc32.h>
-#include <uapi/linux/nfs.h>
-
-/* The LOCALIO program is entirely private to Linux and is
- * NOT part of the uapi.
- */
-#define NFS_LOCALIO_PROGRAM 400122
-#define LOCALIOPROC_NULL 0
-#define LOCALIOPROC_UUID_IS_LOCAL 1
-
-/*
- * This is the kernel NFS client file handle representation
- */
-#define NFS_MAXFHSIZE 128
-struct nfs_fh {
- unsigned short size;
- unsigned char data[NFS_MAXFHSIZE];
-};
-
-/*
- * Returns a zero iff the size and data fields match.
- * Checks only "size" bytes in the data field.
- */
-static inline int nfs_compare_fh(const struct nfs_fh *a, const struct nfs_fh *b)
-{
- return a->size != b->size || memcmp(a->data, b->data, a->size) != 0;
-}
-
-static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *source)
-{
- target->size = source->size;
- memcpy(target->data, source->data, source->size);
-}
-
-enum nfs3_stable_how {
- NFS_UNSTABLE = 0,
- NFS_DATA_SYNC = 1,
- NFS_FILE_SYNC = 2,
+#include <linux/nfs_fh.h>
- /* used by direct.c to mark verf as invalid */
- NFS_INVALID_STABLE_HOW = -1
-};
+#include <uapi/linux/nfs.h>
-/**
- * nfs_fhandle_hash - calculate the crc32 hash for the filehandle
- * @fh - pointer to filehandle
- *
- * returns a crc32 hash for the filehandle that is compatible with
- * the one displayed by "wireshark".
- */
-static inline u32 nfs_fhandle_hash(const struct nfs_fh *fh)
-{
- return ~crc32_le(0xFFFFFFFF, &fh->data[0], fh->size);
-}
#endif /* _LINUX_NFS_H */
diff --git a/include/linux/nfs3.h b/include/linux/nfs3.h
index 404b8f724fc9..1d18da0860d5 100644
--- a/include/linux/nfs3.h
+++ b/include/linux/nfs3.h
@@ -7,6 +7,14 @@
#include <uapi/linux/nfs3.h>
+enum nfs3_stable_how {
+ NFS_UNSTABLE = 0,
+ NFS_DATA_SYNC = 1,
+ NFS_FILE_SYNC = 2,
+
+ /* used to mark verf as invalid */
+ NFS_INVALID_STABLE_HOW = -1
+};
/* Number of 32bit words in post_op_attr */
#define NFS3_POST_OP_ATTR_WORDS 22
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 1a3981c26b23..41b7cdcc674f 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -263,6 +263,12 @@ enum why_no_delegation4 { /* new to v4.1 */
WND4_IS_DIR = 8,
};
+enum stable_how4 {
+ UNSTABLE4 = 0,
+ DATA_SYNC4 = 1,
+ FILE_SYNC4 = 2,
+};
+
enum lock_type4 {
NFS4_UNLOCK_LT = 0,
NFS4_READ_LT = 1,
diff --git a/include/linux/nfs_fh.h b/include/linux/nfs_fh.h
new file mode 100644
index 000000000000..49dfc5ec60fe
--- /dev/null
+++ b/include/linux/nfs_fh.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * struct nfs_fh is an NFS version-agnostic data structure that
+ * stores an NFS file handle. It is also commonly used in NFS
+ * related APIs.
+ */
+#ifndef _LINUX_NFS_FH_H
+#define _LINUX_NFS_FH_H
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/crc32.h>
+
+/*
+ * The largest file handle size today is an NFSv4 file handle,
+ * which can be up to 128 octets long.
+ */
+#define NFS_MAXFHSIZE 128
+struct nfs_fh {
+ unsigned short size;
+ unsigned char data[NFS_MAXFHSIZE];
+};
+
+/**
+ * nfs_compare_fh - Compare two NFS file handles
+ * @a: An NFS file handle to be compared
+ * @b: An NFS file handle to be compared
+ *
+ * Checks only "size" bytes in each data field.
+ *
+ * Return: %false if the two file handles are equal, otherwise %true
+ */
+static inline bool nfs_compare_fh(const struct nfs_fh *a, const struct nfs_fh *b)
+{
+ return a->size != b->size || memcmp(a->data, b->data, a->size) != 0;
+}
+
+/**
+ * nfs_copy_fh - Copy an NFS file handle
+ * @target: Destination file handle
+ * @source: Source file handle
+ *
+ * Copies source->size bytes of file handle data into target.
+ */
+static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *source)
+{
+ target->size = source->size;
+ memcpy(target->data, source->data, source->size);
+}
+
+/**
+ * nfs_fhandle_hash - Calculate the crc32 hash for the filehandle
+ * @fh: An NFS file handle to hash
+ *
+ * Return: a crc32 hash for the filehandle that is compatible with
+ * the one displayed by "wireshark"
+ */
+static inline u32 nfs_fhandle_hash(const struct nfs_fh *fh)
+{
+ return ~crc32_le(0xFFFFFFFF, &fh->data[0], fh->size);
+}
+
+#endif /* _LINUX_NFS_FH_H */
diff --git a/include/linux/nfs_ssc.h b/include/linux/nfs_ssc.h
index 22265b1ff080..c199ea23e7eb 100644
--- a/include/linux/nfs_ssc.h
+++ b/include/linux/nfs_ssc.h
@@ -2,80 +2,33 @@
/*
* include/linux/nfs_ssc.h
*
+ * NFSv4.2 server-to-server copy, NFS client side APIs
+ *
* Author: Dai Ngo <dai.ngo@oracle.com>
*
* Copyright (c) 2020, Oracle and/or its affiliates.
*/
-#include <linux/nfs_fs.h>
-#include <linux/sunrpc/svc.h>
+#ifndef _LINUX_NFS_SSC_H
+#define _LINUX_NFS_SSC_H
-extern struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl;
+#include <linux/nfs_fh.h>
+#include <linux/nfs4.h>
+
+struct file;
+struct vfsmount;
-/*
- * NFS_V4
- */
struct nfs4_ssc_client_ops {
+ struct module *owner;
struct file *(*sco_open)(struct vfsmount *ss_mnt,
struct nfs_fh *src_fh, nfs4_stateid *stateid);
void (*sco_close)(struct file *filep);
};
-/*
- * NFS_FS
- */
-struct nfs_ssc_client_ops {
- void (*sco_sb_deactive)(struct super_block *sb);
-};
-
-struct nfs_ssc_client_ops_tbl {
- const struct nfs4_ssc_client_ops *ssc_nfs4_ops;
- const struct nfs_ssc_client_ops *ssc_nfs_ops;
-};
-
extern void nfs42_ssc_register_ops(void);
extern void nfs42_ssc_unregister_ops(void);
extern void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops);
extern void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops);
-#ifdef CONFIG_NFSD_V4_2_INTER_SSC
-static inline struct file *nfs42_ssc_open(struct vfsmount *ss_mnt,
- struct nfs_fh *src_fh, nfs4_stateid *stateid)
-{
- if (nfs_ssc_client_tbl.ssc_nfs4_ops)
- return (*nfs_ssc_client_tbl.ssc_nfs4_ops->sco_open)(ss_mnt, src_fh, stateid);
- return ERR_PTR(-EIO);
-}
-
-static inline void nfs42_ssc_close(struct file *filep)
-{
- if (nfs_ssc_client_tbl.ssc_nfs4_ops)
- (*nfs_ssc_client_tbl.ssc_nfs4_ops->sco_close)(filep);
-}
-#endif
-
-struct nfsd4_ssc_umount_item {
- struct list_head nsui_list;
- bool nsui_busy;
- /*
- * nsui_refcnt inited to 2, 1 on list and 1 for consumer. Entry
- * is removed when refcnt drops to 1 and nsui_expire expires.
- */
- refcount_t nsui_refcnt;
- unsigned long nsui_expire;
- struct vfsmount *nsui_vfsmount;
- char nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1];
-};
-
-/*
- * NFS_FS
- */
-extern void nfs_ssc_register(const struct nfs_ssc_client_ops *ops);
-extern void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops);
-
-static inline void nfs_do_sb_deactive(struct super_block *sb)
-{
- if (nfs_ssc_client_tbl.ssc_nfs_ops)
- (*nfs_ssc_client_tbl.ssc_nfs_ops->sco_sb_deactive)(sb);
-}
+#endif /* _LINUX_NFS_SSC_H */
diff --git a/include/linux/nfsd_ssc.h b/include/linux/nfsd_ssc.h
new file mode 100644
index 000000000000..7001410f01c2
--- /dev/null
+++ b/include/linux/nfsd_ssc.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * include/linux/nfsd_ssc.h
+ *
+ * NFSv4.2 server-to-server copy, NFS server side APIs
+ *
+ * Author: Dai Ngo <dai.ngo@oracle.com>
+ *
+ * Copyright (c) 2020, Oracle and/or its affiliates.
+ */
+
+#ifndef _LINUX_NFSD_SSC_H
+#define _LINUX_NFSD_SSC_H
+
+#include <linux/nfs_fh.h>
+#include <linux/nfs4.h>
+
+struct file;
+struct vfsmount;
+
+#if IS_ENABLED(CONFIG_NFS_V4_2_SSC_HELPER)
+struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
+ nfs4_stateid *stateid);
+void nfsd42_ssc_close(struct file *filp);
+#else
+static inline struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt,
+ struct nfs_fh *src_fh,
+ nfs4_stateid *stateid)
+{
+ return ERR_PTR(-EIO);
+}
+
+static inline void nfsd42_ssc_close(struct file *filp)
+{
+}
+#endif
+
+#endif /* _LINUX_NFSD_SSC_H */
diff --git a/include/linux/nfslocalio.h b/include/linux/nfslocalio.h
index 3d91043254e6..8ce4d978a636 100644
--- a/include/linux/nfslocalio.h
+++ b/include/linux/nfslocalio.h
@@ -13,9 +13,18 @@
#include <linux/uuid.h>
#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/svcauth.h>
-#include <linux/nfs.h>
+#include <linux/nfs_fh.h>
+
#include <net/net_namespace.h>
+/*
+ * The LOCALIO program is entirely private to Linux and is NOT part of
+ * the uapi.
+ */
+#define NFS_LOCALIO_PROGRAM 400122
+#define LOCALIOPROC_NULL 0
+#define LOCALIOPROC_UUID_IS_LOCAL 1
+
struct nfs_client;
struct nfs_file_localio;
diff --git a/include/trace/misc/nfs.h b/include/trace/misc/nfs.h
index a394b4d38e18..b5fb77d7954b 100644
--- a/include/trace/misc/nfs.h
+++ b/include/trace/misc/nfs.h
@@ -8,6 +8,7 @@
*/
#include <linux/nfs.h>
+#include <linux/nfs3.h>
#include <linux/nfs4.h>
#include <uapi/linux/nfs.h>
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 50e5e7f5b762..7a423e9ee74d 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -238,6 +238,39 @@ static int svc_one_sock_name(struct svc_sock *svsk, char *buf, int remaining)
return len;
}
+/*
+ * kTLS delivers a record only up to the caller's buffer and keeps
+ * the remainder on its receive list, where no further data_ready
+ * announces it. Consume the whole record.
+ */
+static void
+svc_tcp_sock_drain_record(struct socket *sock)
+{
+ union {
+ struct cmsghdr cmsg;
+ u8 buf[CMSG_SPACE(sizeof(u8))];
+ } u;
+ u8 discard[64];
+ struct kvec discard_kvec = {
+ .iov_base = discard,
+ .iov_len = sizeof(discard),
+ };
+
+ for (;;) {
+ struct msghdr msg = {
+ .msg_control = &u,
+ .msg_controllen = sizeof(u),
+ };
+
+ iov_iter_kvec(&msg.msg_iter, ITER_DEST, &discard_kvec, 1,
+ discard_kvec.iov_len);
+ if (sock_recvmsg(sock, &msg, MSG_DONTWAIT) <= 0)
+ break;
+ if (msg.msg_flags & MSG_EOR)
+ break;
+ }
+}
+
static int
svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
struct cmsghdr *cmsg, int ret)
@@ -257,8 +290,17 @@ svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
break;
case TLS_RECORD_TYPE_ALERT:
tls_alert_recv(sock->sk, msg, &level, &description);
- ret = (level == TLS_ALERT_LEVEL_FATAL) ?
- -ENOTCONN : -EAGAIN;
+ /* RFC 8446 Section 6: every alert but a closure alert is
+ * an error alert.
+ */
+ switch (description) {
+ case TLS_ALERT_DESC_CLOSE_NOTIFY:
+ case TLS_ALERT_DESC_USER_CANCELED:
+ ret = -EAGAIN;
+ break;
+ default:
+ ret = -ENOTCONN;
+ }
break;
default:
/* discard this record type */
@@ -289,8 +331,31 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
alert_kvec.iov_len);
ret = sock_recvmsg(sock, &msg, MSG_DONTWAIT);
- if (ret > 0 &&
- tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT) {
+ /* put_cmsg() shrinks msg_controllen, so a short one means
+ * kTLS filled in u.cmsg.
+ */
+ if (ret >= 0 && msg.msg_controllen < sizeof(u)) {
+ u8 content_type = tls_get_record_type(sock->sk, &u.cmsg);
+
+ /* Returning the count would credit the RPC stream with
+ * octets that never reached the caller's buffer.
+ */
+ if (content_type != TLS_RECORD_TYPE_ALERT) {
+ /* An application data record carries RPC payload.
+ * Draining one breaks RPC fragment framing.
+ */
+ if (content_type != TLS_RECORD_TYPE_DATA &&
+ !(msg.msg_flags & MSG_EOR))
+ svc_tcp_sock_drain_record(sock);
+ return -EAGAIN;
+ }
+ /* An Alert record carries exactly one two-octet message
+ * (RFC 8446 Section 5.1). alert_kvec caps the receive at two,
+ * so a longer record produces the same count. MSG_EOR appears
+ * only once kTLS has drained the whole record.
+ */
+ if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR))
+ return -EBADMSG;
iov_iter_revert(&msg.msg_iter, ret);
ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN);
}
@@ -306,8 +371,16 @@ svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg)
ret = sock_recvmsg(sock, msg, MSG_DONTWAIT);
if (msg->msg_flags & MSG_CTRUNC) {
msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
- if (ret == 0 || ret == -EIO)
+ if (ret == 0 || ret == -EIO) {
ret = svc_tcp_sock_recv_cmsg(sock, &msg->msg_flags);
+ /* A control record delivers nothing to the caller,
+ * and kTLS announces no data_ready for records it
+ * already holds. Mark the transport ready so that
+ * the records behind this one can be received.
+ */
+ if (ret == -EAGAIN)
+ set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
+ }
}
return ret;
}
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index 7f60723fa64d..ca22ca8f2fea 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -357,35 +357,6 @@ xs_alloc_sparse_pages(struct xdr_buf *buf, size_t want, gfp_t gfp)
}
static int
-xs_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
- unsigned int *msg_flags, struct cmsghdr *cmsg, int ret)
-{
- u8 content_type = tls_get_record_type(sock->sk, cmsg);
- u8 level, description;
-
- switch (content_type) {
- case 0:
- break;
- case TLS_RECORD_TYPE_DATA:
- /* TLS sets EOR at the end of each application data
- * record, even though there might be more frames
- * waiting to be decrypted.
- */
- *msg_flags &= ~MSG_EOR;
- break;
- case TLS_RECORD_TYPE_ALERT:
- tls_alert_recv(sock->sk, msg, &level, &description);
- ret = (level == TLS_ALERT_LEVEL_FATAL) ?
- -EACCES : -EAGAIN;
- break;
- default:
- /* discard this record type */
- ret = -EAGAIN;
- }
- return ret;
-}
-
-static int
xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags)
{
union {
@@ -402,16 +373,39 @@ xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags)
.msg_control = &u,
.msg_controllen = sizeof(u),
};
+ u8 level, description;
int ret;
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
alert_kvec.iov_len);
ret = sock_recvmsg(sock, &msg, flags);
- if (ret > 0) {
- if (tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT)
- iov_iter_revert(&msg.msg_iter, ret);
- ret = xs_sock_process_cmsg(sock, &msg, msg_flags, &u.cmsg,
- -EAGAIN);
+ /* put_cmsg() shrinks msg_controllen, so a short one means
+ * kTLS filled in u.cmsg.
+ */
+ if (ret >= 0 && msg.msg_controllen < sizeof(u)) {
+ if (tls_get_record_type(sock->sk, &u.cmsg) !=
+ TLS_RECORD_TYPE_ALERT)
+ return -EAGAIN;
+ /* An Alert record carries exactly one two-octet message
+ * (RFC 8446 Section 5.1). alert_kvec caps the receive at
+ * two, so the count alone cannot reliably detect an
+ * oversized record.
+ */
+ if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR))
+ return -EACCES;
+ iov_iter_revert(&msg.msg_iter, ret);
+ tls_alert_recv(sock->sk, &msg, &level, &description);
+ /* RFC 8446 Section 6: every alert but a closure alert is
+ * an error alert.
+ */
+ switch (description) {
+ case TLS_ALERT_DESC_CLOSE_NOTIFY:
+ case TLS_ALERT_DESC_USER_CANCELED:
+ ret = -EAGAIN;
+ break;
+ default:
+ ret = -EACCES;
+ }
}
return ret;
}
@@ -425,6 +419,10 @@ xs_sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags, size_t seek)
ret = sock_recvmsg(sock, msg, flags);
/* Handle TLS inband control message lazily */
if (msg->msg_flags & MSG_CTRUNC) {
+ /* TLS sets EOR at the end of each application data
+ * record, even though there might be more frames
+ * waiting to be decrypted.
+ */
msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
if (ret == 0 || ret == -EIO)
ret = xs_sock_recv_cmsg(sock, &msg->msg_flags, flags);