Linux NFS development
 help / color / mirror / Atom feed
From: Jeff Layton <jlayton@kernel.org>
To: Steve Dickson <steved@redhat.com>, Chuck Lever <cel@kernel.org>,
	 NeilBrown <neil@brown.name>,
	Olga Kornievskaia <okorniev@redhat.com>,
	 Dai Ngo <Dai.Ngo@oracle.com>, Tom Talpey <tom@talpey.com>,
	 Trond Myklebust <trondmy@kernel.org>,
	Anna Schumaker <anna@kernel.org>,
	 Mike Snitzer <snitzer@kernel.org>
Cc: linux-nfs@vger.kernel.org, Jeff Layton <jlayton@kernel.org>
Subject: [PATCH nfs-utils v2 2/4] exports: Add read_io= and write_io= export options
Date: Thu, 29 Oct 2026 08:10:09 -0400	[thread overview]
Message-ID: <20261029-master-v2-2-32bfc6ac16da@kernel.org> (raw)
In-Reply-To: <20261029-master-v2-0-32bfc6ac16da@kernel.org>

Parse read_io=/write_io= (buffered/dontcache/direct) into new
e_read_io/e_write_io exportent fields, defaulting to unset, and pass
explicitly-set values to the kernel over the netlink svc_export
interface.

- Kernels without the I/O mode attributes (e.g. v7.2) reject the whole
  entry. Add nfsd_export_iomode_supported(), which checks the
  svc_export policy's highest attribute via nfsd_export_policy_query(),
  and have mountd drop the options with a warning on such kernels
  instead of leaving the export unmountable.
- The /proc channel cannot carry them either; warn there too.
- Resync nfsd_netlink.h and bump the configure probe to
  NFSD_A_SVC_EXPORT_WRITE_IO.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 configure.ac                   |  2 +-
 support/export/cache.c         | 59 +++++++++++++++++++++++++++++++++++++++++-
 support/export/export_policy.c | 22 ++++++++++++++++
 support/export/v4root.c        |  2 ++
 support/include/exportfs.h     |  1 +
 support/include/nfsd_netlink.h | 12 +++++++++
 support/include/nfslib.h       | 13 ++++++++++
 support/nfs/exports.c          | 51 ++++++++++++++++++++++++++++++++++++
 8 files changed, 160 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index 2c13292961bf..73230a2a637e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -283,7 +283,7 @@ AC_CHECK_HEADERS(linux/nfsd_netlink.h)
 
 # ensure the system netlink headers have the latest features
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <linux/nfsd_netlink.h>]],
-			                   [[int foo = NFSD_CMD_FILEHANDLE_GET;]])],
+			                   [[int foo = NFSD_A_SVC_EXPORT_WRITE_IO;]])],
 			   [AC_DEFINE([USE_SYSTEM_NFSD_NETLINK_H], 1,
 				      ["Use system's linux/nfsd_netlink.h"])])
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <linux/lockd_netlink.h>]],
diff --git a/support/export/cache.c b/support/export/cache.c
index 3c7ee9f90ac1..1ee94996948a 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -1287,6 +1287,9 @@ static struct nl_sock *nfsd_nl_notify_sock;	/* multicast notifications */
 static struct nl_sock *nfsd_nl_cmd_sock;	/* GET_REQS / SET_REQS commands */
 static int nfsd_nl_family;
 
+/* Does the running kernel accept read_io/write_io in SET_REQS? */
+static bool nfsd_nl_iomode_ok;
+
 #define NL_BUFFER_SIZE	65536
 
 struct export_req {
@@ -1380,8 +1383,10 @@ static int cache_nfsd_nl_open(void)
 	ret = cache_genl_open(NFSD_FAMILY_NAME, NFSD_MCGRP_EXPORTD,
 			      &nfsd_nl_cmd_sock, &nfsd_nl_notify_sock,
 			      &nfsd_nl_family);
-	if (ret == 0)
+	if (ret == 0) {
 		nfsd_nl_load_export_features();
+		nfsd_nl_iomode_ok = nfsd_export_iomode_supported() > 0;
+	}
 	return ret;
 }
 
@@ -1648,6 +1653,40 @@ static int nfsd_nl_add_xprtsec(struct nl_msg *msg, struct exportent *ep)
 	return 0;
 }
 
+static int nfsd_nl_add_iomode(struct nl_msg *msg, struct exportent *ep)
+{
+	static bool warned;
+
+	if (ep->e_read_io == NFSD_IO_UNSET && ep->e_write_io == NFSD_IO_UNSET)
+		return 0;
+
+	/*
+	 * A kernel without per-export I/O modes would refuse the entry,
+	 * leaving the export unmountable.  Export it with the server's
+	 * global I/O modes instead.
+	 */
+	if (!nfsd_nl_iomode_ok) {
+		if (!warned) {
+			xlog(L_WARNING, "%s: kernel does not support read_io= "
+			     "or write_io=, ignoring", ep->e_path);
+			warned = true;
+		}
+		return 0;
+	}
+
+	if (ep->e_read_io != NFSD_IO_UNSET &&
+	    nla_put_u32(msg, NFSD_A_SVC_EXPORT_READ_IO,
+			ep->e_read_io) < 0)
+		return -1;
+
+	if (ep->e_write_io != NFSD_IO_UNSET &&
+	    nla_put_u32(msg, NFSD_A_SVC_EXPORT_WRITE_IO,
+			ep->e_write_io) < 0)
+		return -1;
+
+	return 0;
+}
+
 /*
  * Add one svc_export response.  @ea must be the attributes computed by
  * export_attrs_build() for (@path, @exp), and is ignored when @exp is NULL.
@@ -1704,6 +1743,9 @@ static int nfsd_nl_add_export(struct nl_msg *msg, char *domain, char *path,
 
 		if (nfsd_nl_add_xprtsec(msg, exp))
 			goto nla_failure;
+
+		if (nfsd_nl_add_iomode(msg, exp))
+			goto nla_failure;
 	}
 
 	nla_nest_end(msg, nest);
@@ -3261,6 +3303,20 @@ static void cache_sunrpc_nl_process(void)
 		cache_nl_process_unix_gid();
 }
 
+/* The kernel takes per-export I/O modes only over netlink */
+static void warn_iomode_unsupported(struct exportent *exp)
+{
+	static bool warned;
+
+	if (warned || (exp->e_read_io == NFSD_IO_UNSET &&
+		       exp->e_write_io == NFSD_IO_UNSET))
+		return;
+
+	xlog(L_WARNING, "%s: read_io= and write_io= need the netlink "
+	     "export interface, ignoring", exp->e_path);
+	warned = true;
+}
+
 static int dump_to_cache(int f, char *buf, int blen, char *domain,
 			 char *path, struct exportent *exp, int ttl)
 {
@@ -3293,6 +3349,7 @@ static int dump_to_cache(int f, char *buf, int blen, char *domain,
 			qword_addhex(&bp, &blen, ea.uuid, 16);
 		}
 		write_xprtsec(&bp, &blen, exp);
+		warn_iomode_unsupported(exp);
 		xlog(D_AUTH, "granted access to %s for %s",
 		     path, *domain == '$' ? domain+1 : domain);
 	} else {
diff --git a/support/export/export_policy.c b/support/export/export_policy.c
index a0170cb87f91..fce999a4f823 100644
--- a/support/export/export_policy.c
+++ b/support/export/export_policy.c
@@ -244,6 +244,28 @@ int nfsd_export_policy_query(struct nfsd_export_policy *UNUSED(pol))
 
 #endif /* HAVE_GENL_POLICY_DUMP */
 
+/**
+ * nfsd_export_iomode_supported - does the kernel take per-export I/O modes?
+ *
+ * A kernel without them refuses any svc_export entry that carries one.
+ *
+ * Returns 1 if it accepts read_io/write_io, 0 if not or if that cannot be
+ * found out, or -1 if the nfsd netlink family is not registered, as when
+ * nfsd is not loaded.
+ */
+int nfsd_export_iomode_supported(void)
+{
+	struct nfsd_export_policy pol;
+	int ret;
+
+	ret = nfsd_export_policy_query(&pol);
+	if (ret == -ENOENT)
+		return -1;
+	if (ret < 0)
+		return 0;
+	return pol.max_attr >= NFSD_A_SVC_EXPORT_WRITE_IO;
+}
+
 /**
  * nfsd_nl_load_export_features - learn the kernel's export flags over netlink
  *
diff --git a/support/export/v4root.c b/support/export/v4root.c
index 38286b91ede0..f7f1ca38c9a9 100644
--- a/support/export/v4root.c
+++ b/support/export/v4root.c
@@ -47,6 +47,8 @@ static nfs_export pseudo_root = {
 		.e_fsid = 0,
 		.e_mountpoint = NULL,
 		.e_ttl = 0,
+		.e_read_io = NFSD_IO_UNSET,
+		.e_write_io = NFSD_IO_UNSET,
 	},
 	.m_exported = 0,
 	.m_xtabent = 1,
diff --git a/support/include/exportfs.h b/support/include/exportfs.h
index a7c145543075..9096db37a6ee 100644
--- a/support/include/exportfs.h
+++ b/support/include/exportfs.h
@@ -177,6 +177,7 @@ struct nfsd_export_policy {
 struct nfsd_export_policy *get_export_features(void);
 void set_export_features(const struct nfsd_export_policy *pol);
 int nfsd_export_policy_query(struct nfsd_export_policy *pol);
+int nfsd_export_iomode_supported(void);
 void nfsd_nl_load_export_features(void);
 
 void fix_pseudoflavor_flags(struct exportent *ep);
diff --git a/support/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
index dcd2126d848b..f67efa23997f 100644
--- a/support/include/nfsd_netlink.h
+++ b/support/include/nfsd_netlink.h
@@ -59,6 +59,16 @@ enum nfsd_rpcbind_flags {
 	NFSD_RPCBIND_FLAGS_NO_UDP = 1,
 };
 
+/*
+ * NFS READ/WRITE I/O mode. These values match the global io_cache_read and
+ * io_cache_write debugfs settings.
+ */
+enum nfsd_io_mode {
+	NFSD_IO_MODE_BUFFERED,
+	NFSD_IO_MODE_DONTCACHE,
+	NFSD_IO_MODE_DIRECT,
+};
+
 enum {
 	NFSD_A_CACHE_NOTIFY_CACHE_TYPE = 1,
 
@@ -185,6 +195,8 @@ enum {
 	NFSD_A_SVC_EXPORT_XPRTSEC,
 	NFSD_A_SVC_EXPORT_FLAGS,
 	NFSD_A_SVC_EXPORT_FSID,
+	NFSD_A_SVC_EXPORT_READ_IO,
+	NFSD_A_SVC_EXPORT_WRITE_IO,
 
 	__NFSD_A_SVC_EXPORT_MAX,
 	NFSD_A_SVC_EXPORT_MAX = (__NFSD_A_SVC_EXPORT_MAX - 1)
diff --git a/support/include/nfslib.h b/support/include/nfslib.h
index c8601a156cba..7d7aadaed159 100644
--- a/support/include/nfslib.h
+++ b/support/include/nfslib.h
@@ -75,6 +75,16 @@ struct xprtsec_entry {
 	int			flags;
 };
 
+/*
+ * Per-export I/O modes for read_io=/write_io=. Values must match the
+ * kernel's NFSD_IO_* enum (fs/nfsd/nfsd.h). NFSD_IO_UNSET means no
+ * option was given; the export follows the server's global setting.
+ */
+#define NFSD_IO_BUFFERED	0
+#define NFSD_IO_DONTCACHE	1
+#define NFSD_IO_DIRECT		2
+#define NFSD_IO_UNSET		(-1)
+
 /*
  * Data related to a single exports entry as returned by getexportent.
  * FIXME: export options should probably be parsed at a later time to
@@ -100,6 +110,8 @@ struct exportent {
 	unsigned int	e_ttl;
 	char *		e_realpath;
 	int		e_reexport;
+	int		e_read_io;
+	int		e_write_io;
 };
 
 struct rmtabent {
@@ -115,6 +127,7 @@ void			setexportent(char *fname, char *type);
 struct exportent *	getexportent(int);
 void 			secinfo_show(FILE *fp, struct exportent *ep);
 void			xprtsecinfo_show(FILE *fp, struct exportent *ep);
+const char *		iomode_name(int mode);
 void			putexportent(struct exportent *xep);
 void			endexportent(void);
 struct exportent *	mkexportent(char *hname, char *path, char *opts);
diff --git a/support/nfs/exports.c b/support/nfs/exports.c
index 5ccc4ca0582f..ef2eb7b766ad 100644
--- a/support/nfs/exports.c
+++ b/support/nfs/exports.c
@@ -107,6 +107,8 @@ static void init_exportent (struct exportent *ee, int fromkernel)
 	ee->e_uuid = NULL;
 	ee->e_ttl = default_ttl;
 	ee->e_reexport = REEXP_NONE;
+	ee->e_read_io = NFSD_IO_UNSET;
+	ee->e_write_io = NFSD_IO_UNSET;
 }
 
 struct exportent *
@@ -313,6 +315,10 @@ putexportent(struct exportent *ep)
 	if (ep->e_flags & NFSEXP_SIGN_FH)
 		fprintf(fp, "sign_fh,");
 	fprintf(fp, "%spnfs,", (ep->e_flags & NFSEXP_PNFS)? "" : "no_");
+	if (ep->e_read_io != NFSD_IO_UNSET)
+		fprintf(fp, "read_io=%s,", iomode_name(ep->e_read_io));
+	if (ep->e_write_io != NFSD_IO_UNSET)
+		fprintf(fp, "write_io=%s,", iomode_name(ep->e_write_io));
 	if (ep->e_flags & NFSEXP_FSID) {
 		fprintf(fp, "fsid=%d,", ep->e_fsid);
 	}
@@ -586,6 +592,45 @@ static unsigned int parse_xprtsec(char *str, struct exportent *ep)
 	return out;
 }
 
+/*
+ * Parse the value of a read_io= or write_io= option into @mode.
+ *
+ * A zero return indicates an error.
+ */
+static int parse_iomode(char *str, int *mode)
+{
+	char *flname = efname ? efname : "command line";
+	int flline = efp ? efp->x_line : 0;
+
+	if (!strcmp(str, "buffered"))
+		*mode = NFSD_IO_BUFFERED;
+	else if (!strcmp(str, "dontcache"))
+		*mode = NFSD_IO_DONTCACHE;
+	else if (!strcmp(str, "direct"))
+		*mode = NFSD_IO_DIRECT;
+	else {
+		xlog(L_ERROR, "%s:%d: bad iomode \"%s\"\n",
+		     flname, flline, str);
+		return 0;
+	}
+	return 1;
+}
+
+const char *
+iomode_name(int mode)
+{
+	switch (mode) {
+	case NFSD_IO_BUFFERED:
+		return "buffered";
+	case NFSD_IO_DONTCACHE:
+		return "dontcache";
+	case NFSD_IO_DIRECT:
+		return "direct";
+	default:
+		return "unknown";
+	}
+}
+
 /* Sets the bits in @mask for the appropriate security flavor flags. */
 static void setflags(int mask, unsigned int active, struct exportent *ep)
 {
@@ -804,6 +849,12 @@ bad_option:
 		} else if (strncmp(opt, "xprtsec=", 8) == 0) {
 			if (!parse_xprtsec(opt + 8, ep))
 				goto bad_option;
+		} else if (strncmp(opt, "read_io=", 8) == 0) {
+			if (!parse_iomode(opt + 8, &ep->e_read_io))
+				goto bad_option;
+		} else if (strncmp(opt, "write_io=", 9) == 0) {
+			if (!parse_iomode(opt + 9, &ep->e_write_io))
+				goto bad_option;
 		} else if (strncmp(opt, "reexport=", 9) == 0) {
 			char *strategy = strchr(opt, '=');
 

-- 
2.56.0


  parent reply	other threads:[~2026-10-29 12:10 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 12:10 [PATCH nfs-utils v2 0/4] exports: add support for new read_io= and write_io= export options Jeff Layton
2026-10-29 12:10 ` [PATCH nfs-utils v2 1/4] exportfs/mountd/exportd: track the svc_export policy highest attribute Jeff Layton
2026-10-29 12:10 ` Jeff Layton [this message]
2026-10-29 12:10 ` [PATCH nfs-utils v2 3/4] exportfs: Display and document read_io= and write_io= Jeff Layton
2026-10-29 12:10 ` [PATCH nfs-utils v2 4/4] nfsd-iomode-advisor: recommend read_io= and write_io= for an export Jeff Layton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261029-master-v2-2-32bfc6ac16da@kernel.org \
    --to=jlayton@kernel.org \
    --cc=Dai.Ngo@oracle.com \
    --cc=anna@kernel.org \
    --cc=cel@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=okorniev@redhat.com \
    --cc=snitzer@kernel.org \
    --cc=steved@redhat.com \
    --cc=tom@talpey.com \
    --cc=trondmy@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox