All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.