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
next prev 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