From: Jeff Layton <jlayton@kernel.org>
To: "Steve Dickson" <steved@redhat.com>,
"Mantas Mikulėnas" <grawity@gmail.com>
Cc: Chuck Lever <cel@kernel.org>,
linux-nfs@vger.kernel.org, Jeff Layton <jlayton@kernel.org>
Subject: [PATCH nfs-utils v3 03/11] mountd: answer requests the kernel rejects on the netlink downcall
Date: Mon, 14 Sep 2026 09:14:12 -0400 [thread overview]
Message-ID: <20260914-nl-crossmnt-v3-3-a984a6c94829@kernel.org> (raw)
In-Reply-To: <20260914-nl-crossmnt-v3-0-a984a6c94829@kernel.org>
The kernel refuses an svc_export it cannot build a filehandle for - a 9p
submount picked up by crossmnt, say - and it fails the whole SET_REQS
message when it does. cache_nl_process_export() batches every pending
request into one message and never looks at the result, so:
- every entry queued behind the bad one is dropped
- nothing downgrades the bad path to a negative entry
- the request stays pending and the client hangs on the lookup
Pending requests then pile up on each notification:
cache_nl_process_export: 5 pending export requests
cache_nl_set_reqs: SET_REQS failed: -7
nfsd_export() has no such hole: a rejected dump_to_cache() write returns
-1 and it answers negative instead.
Keep the batch for the fast path, but resubmit it one entry at a time
when the kernel rejects it, and answer negative for whichever entries it
still refuses. nfsd_nl_svc_export_set_reqs_doit() commits each entry as
it parses it and stops at the first failure, so the resubmit re-sends
some entries the kernel already took; that is harmless, as the update is
idempotent, and it is the only way to find the one that failed.
nl_add_export_req() no longer flushes the message itself; the caller
owns the batch so it knows what to resubmit.
Only fall back to a negative entry when the kernel actually answered.
libnl folds its own errors into the same NLE_* space as the kernel's, so
cache_nl_set_reqs() now reports the kernel's errno separately: a broken
socket must not cache "not exported" for default_ttl. A negative entry
the kernel already refused - the path or the client's auth_domain went
away - is not resent as another negative either.
An entry too large for a message of its own is a third case. mountd
never sent it, so the kernel never refused it. Log the size failure and
answer negative, rather than blame the filesystem for it.
The resubmit makes a refusal routine, and the kernel tends to notify one
request at a time, so warning about it printed the same failure twice per
bad path. Trace it at D_NETLINK instead, with nl_geterror() and the
kernel's errno rather than a raw number: the -7 above is NLE_INVAL, libnl's
own code for the kernel's EINVAL, not an errno. Callers that have no
fallback keep their warning through the new cache_nl_flush_reqs(), which
ip_map and unix_gid now use, and the -ENOMEM in cache_nl_set_reqs() becomes
-NLE_NOMEM so the value is in the space nl_geterror() reads.
A path nobody asked to export - /proc or /sys below a crossmnt "/" - is
expected to be refused and the admin cannot act on it, so the negative
fallback only warns for a path exported in its own right.
Reported-by: Mantas Mikulėnas <grawity@gmail.com>
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
support/export/cache.c | 367 +++++++++++++++++++++++++++++++++++++++----------
1 file changed, 294 insertions(+), 73 deletions(-)
diff --git a/support/export/cache.c b/support/export/cache.c
index 0ad98c539407..5bdc00c5847f 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -1623,18 +1623,59 @@ static struct nl_msg *cache_nl_new_msg(int family, int cmd, int flags)
return msg;
}
-static int cache_nl_set_reqs(struct nl_sock *sock, struct nl_msg *msg)
+/* State for one SET_REQS round trip */
+struct set_reqs_status {
+ int done;
+ int kern_err; /* errno the kernel answered with */
+};
+
+static int nl_set_reqs_finish_cb(struct nl_msg *UNUSED(msg), void *arg)
+{
+ struct set_reqs_status *st = arg;
+
+ st->done = 1;
+ return NL_STOP;
+}
+
+static int nl_set_reqs_error_cb(struct sockaddr_nl *UNUSED(nla),
+ struct nlmsgerr *nlerr, void *arg)
+{
+ struct set_reqs_status *st = arg;
+
+ st->done = 1;
+ st->kern_err = nlerr->error;
+ return NL_STOP;
+}
+
+/*
+ * Send @msg and wait for the kernel to ack it. Returns 0 on success.
+ *
+ * libnl folds the kernel's errno into its own NLE_* space, and a local
+ * failure lands in the same space, so the return value cannot say whether
+ * the kernel looked at the message at all. When @kern_errp is given it is
+ * set to the negative errno the kernel replied with, or left at 0 if it
+ * never got that far.
+ *
+ * A refusal is routine - the batch path uses one to find the entry the
+ * kernel would not take - so only trace it here and leave it to the caller
+ * to decide what is worth a warning.
+ */
+static int cache_nl_set_reqs(struct nl_sock *sock, struct nl_msg *msg,
+ int *kern_errp)
{
+ struct set_reqs_status st = {};
struct nl_cb *cb;
- int done = 0;
int ret;
+ if (kern_errp)
+ *kern_errp = 0;
+
cb = nl_cb_alloc(NL_CB_DEFAULT);
if (!cb)
- return -ENOMEM;
+ return -NLE_NOMEM;
- nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_finish_cb, &done);
- nl_cb_err(cb, NL_CB_CUSTOM, nl_error_cb, &done);
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_set_reqs_finish_cb, &st);
+ nl_cb_err(cb, NL_CB_CUSTOM, nl_set_reqs_error_cb, &st);
ret = nl_send_auto(sock, msg);
if (ret < 0) {
@@ -1642,15 +1683,19 @@ static int cache_nl_set_reqs(struct nl_sock *sock, struct nl_msg *msg)
return ret;
}
- while (!done) {
+ while (!st.done) {
ret = nl_recvmsgs(sock, cb);
if (ret < 0)
break;
}
nl_cb_put(cb);
+ if (kern_errp)
+ *kern_errp = st.kern_err;
if (ret < 0)
- xlog(L_WARNING, "%s: SET_REQS failed: %d", __func__, ret);
+ xlog(D_NETLINK, "%s: SET_REQS failed: %s (kernel: %s)",
+ __func__, nl_geterror(ret),
+ st.kern_err ? strerror(-st.kern_err) : "no answer");
return ret;
}
@@ -1659,24 +1704,54 @@ static bool nl_msg_has_reqs(struct nl_msg *msg)
return genlmsg_attrlen(nlmsg_data(nlmsg_hdr(msg)), 0) > 0;
}
+/*
+ * Send a message the caller has no fallback for. There is nothing to try
+ * instead when the kernel refuses an ip_map or unix_gid answer, so warn.
+ */
+static void cache_nl_flush_reqs(struct nl_sock *sock, struct nl_msg *msg,
+ const char *what)
+{
+ int ret;
+
+ if (!nl_msg_has_reqs(msg))
+ return;
+
+ ret = cache_nl_set_reqs(sock, msg, NULL);
+ if (ret < 0)
+ xlog(L_WARNING, "failed to answer %s requests: %s",
+ what, nl_geterror(ret));
+}
+
enum export_result {
- EXPORT_ANSWERED,
+ EXPORT_ANSWERED, /* positive entry added */
+ EXPORT_DENIED, /* negative entry added */
EXPORT_RETRY, /* not resolvable yet, ask again later */
- EXPORT_NOMEM, /* *msgp is gone, caller must give up */
+ EXPORT_FULL, /* did not fit, flush the message and re-add */
};
/*
- * Resolve one svc_export request and append the answer to *msgp, sending
- * and replacing the message if it fills up.
+ * Did the admin ask for @path itself, or did we get here from a crossmnt
+ * parent or the v4 pseudoroot? A filesystem nobody asked to export - /proc
+ * or /sys below a crossmnt "/", say - is expected to be unexportable, and
+ * saying so once per TTL is just noise.
+ */
+static bool export_is_explicit(nfs_export *found, char *path)
+{
+ return found && !strcmp(found->m_export.e_path, path);
+}
+
+/*
+ * Resolve one svc_export request and append the answer to @msg. @explicitp,
+ * when given, says whether @path is an export in its own right.
*/
-static enum export_result nl_add_export_req(struct nl_msg **msgp, char *dom,
- char *path)
+static enum export_result nl_add_export_req(struct nl_msg *msg, char *dom,
+ char *path, bool *explicitp)
{
struct addrinfo *ai = NULL;
nfs_export *found = NULL;
struct exportent *epp = NULL;
struct exportent *junction = NULL;
- enum export_result res = EXPORT_ANSWERED;
+ enum export_result res;
int ttl = 0;
if (is_ipaddr_client(dom)) {
@@ -1694,6 +1769,9 @@ static enum export_result nl_add_export_req(struct nl_msg **msgp, char *dom,
}
}
+ if (explicitp)
+ *explicitp = export_is_explicit(found, path);
+
if (found) {
char *mp = found->m_export.e_mountpoint;
@@ -1722,25 +1800,137 @@ static enum export_result nl_add_export_req(struct nl_msg **msgp, char *dom,
}
}
- if (nfsd_nl_add_export(*msgp, dom, path, epp, ttl) < 0) {
- cache_nl_set_reqs(nfsd_nl_cmd_sock, *msgp);
- nlmsg_free(*msgp);
- *msgp = cache_nl_new_msg(nfsd_nl_family,
- NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
- if (!*msgp) {
- res = EXPORT_NOMEM;
- goto out;
- }
- if (nfsd_nl_add_export(*msgp, dom, path, epp, ttl) < 0)
- xlog(L_WARNING, "%s: skipping oversized entry for %s",
- __func__, path);
- }
+ if (nfsd_nl_add_export(msg, dom, path, epp, ttl) < 0)
+ res = EXPORT_FULL;
+ else
+ res = epp ? EXPORT_ANSWERED : EXPORT_DENIED;
out:
free_junction(junction);
nfs_freeaddrinfo(ai);
return res;
}
+/*
+ * Answer @path negative in a message of its own. Returns EXPORT_ANSWERED once
+ * the kernel has seen the answer - including when it refused it, as a second
+ * attempt would fare no better - and EXPORT_RETRY when the answer never got
+ * that far and is worth sending again.
+ */
+static enum export_result nl_export_negative(char *dom, char *path)
+{
+ struct nl_msg *msg;
+ int kern_err = 0;
+ int ret;
+
+ msg = cache_nl_new_msg(nfsd_nl_family,
+ NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
+ if (!msg)
+ return EXPORT_RETRY;
+
+ if (nfsd_nl_add_export(msg, dom, path, NULL, 0) < 0) {
+ nlmsg_free(msg);
+ return EXPORT_RETRY;
+ }
+
+ ret = cache_nl_set_reqs(nfsd_nl_cmd_sock, msg, &kern_err);
+ nlmsg_free(msg);
+
+ if (ret < 0 && !kern_err)
+ return EXPORT_RETRY;
+ return EXPORT_ANSWERED;
+}
+
+/*
+ * Errors the kernel gives for an export it can never accept. check_export()
+ * answers EINVAL for a filesystem with no export ops, one that needs an fsid=
+ * and has none, and an idmapped mount; ENOTDIR is an inode that is neither a
+ * directory, a symlink, nor a regular file. Anything else it can return -
+ * ENOENT for an auth_domain or a path that is not there yet, ENOMEM, ENODEV
+ * while nfsd shuts down - may well work on the next try.
+ */
+static bool kernel_refused_for_good(int kern_err)
+{
+ switch (kern_err) {
+ case -EINVAL:
+ case -ENOTDIR:
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Answer one request in a message of its own. The kernel rejects an export
+ * it cannot build a filehandle for - a 9p or other filesystem with no export
+ * ops, say - so fall back to a negative entry rather than leaving the request
+ * pending, which is what dump_to_cache() does when the channel write fails.
+ */
+static enum export_result nl_export_one(char *dom, char *path)
+{
+ bool explicit_export = false;
+ enum export_result res;
+ struct nl_msg *msg;
+ int kern_err = 0;
+ bool sent;
+
+ msg = cache_nl_new_msg(nfsd_nl_family,
+ NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
+ if (!msg)
+ return EXPORT_RETRY;
+
+ res = nl_add_export_req(msg, dom, path, &explicit_export);
+ if (res == EXPORT_RETRY) {
+ nlmsg_free(msg);
+ return res;
+ }
+
+ /*
+ * An entry that does not fit a message of its own can never be sent,
+ * and this is not the kernel refusing it. Answer negative rather
+ * than leave the client hung on a request we cannot satisfy.
+ */
+ if (res == EXPORT_FULL) {
+ nlmsg_free(msg);
+ xlog(L_WARNING, "%s: entry for %s is too large to send",
+ __func__, path);
+ return nl_export_negative(dom, path);
+ }
+
+ sent = cache_nl_set_reqs(nfsd_nl_cmd_sock, msg, &kern_err) == 0;
+ nlmsg_free(msg);
+
+ if (sent)
+ return EXPORT_ANSWERED;
+
+ /*
+ * The kernel never answered - a broken socket, or a message we could
+ * not send - so we cannot tell whether the export is usable. Retry
+ * rather than cache a negative entry for default_ttl.
+ */
+ if (!kern_err)
+ return EXPORT_RETRY;
+
+ /*
+ * It may recover from this one. Denying the path would hide a working
+ * export for default_ttl, and a negative entry needs the very
+ * auth_domain the kernel may have just failed to find, so it would
+ * likely be refused as well. Ask again later instead.
+ */
+ if (!kernel_refused_for_good(kern_err)) {
+ xlog(D_GENERAL, "%s: kernel refused %s: %s, will retry",
+ __func__, path, strerror(-kern_err));
+ return EXPORT_RETRY;
+ }
+
+ /* It refused a negative entry; a second one will fare no better */
+ if (res == EXPORT_DENIED)
+ return EXPORT_ANSWERED;
+
+ xlog(explicit_export ? L_WARNING : D_GENERAL,
+ "Cannot export %s, possibly unsupported filesystem"
+ " or fsid= required", path);
+ return nl_export_negative(dom, path);
+}
+
/*
* is_mountpoint() can fail with a strange error - the ETIMEDOUT a re-exported
* "softerr" NFS mount can give, say - leaving us unable to say whether the
@@ -1783,6 +1973,23 @@ static void delayed_export_flush(void)
}
}
+/* Forget any deferred request for @dom and @path; it has been answered */
+static void delayed_export_remove(char *dom, char *path)
+{
+ struct delayed_export **dp = &delayed_export;
+
+ while (*dp) {
+ struct delayed_export *d = *dp;
+
+ if (!strcmp(d->client, dom) && !strcmp(d->path, path)) {
+ *dp = d->next;
+ delayed_export_free(d);
+ return;
+ }
+ dp = &d->next;
+ }
+}
+
static void nl_delay_export(char *dom, char *path)
{
struct delayed_export *d;
@@ -1812,7 +2019,6 @@ static void nl_delay_export(char *dom, char *path)
static void nl_retry_export(void)
{
struct delayed_export *d = delayed_export;
- struct nl_msg *msg;
if (!d || d->last_attempt + RETRY_SEC > time(NULL))
return;
@@ -1822,35 +2028,33 @@ static void nl_retry_export(void)
auth_reload();
- msg = cache_nl_new_msg(nfsd_nl_family,
- NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
- if (!msg) {
+ if (nl_export_one(d->client, d->path) == EXPORT_RETRY)
delayed_export_enqueue(d);
- return;
- }
-
- switch (nl_add_export_req(&msg, d->client, d->path)) {
- case EXPORT_ANSWERED:
- if (nl_msg_has_reqs(msg))
- cache_nl_set_reqs(nfsd_nl_cmd_sock, msg);
+ else
delayed_export_free(d);
- break;
- case EXPORT_RETRY:
- delayed_export_enqueue(d);
- break;
- case EXPORT_NOMEM:
- delayed_export_enqueue(d);
- return; /* msg is already gone */
- }
- nlmsg_free(msg);
+}
+
+/*
+ * Answer [@start, @end) one at a time. Building the batch may already have
+ * deferred some of these, so an entry that resolves this time has to come back
+ * off the retry queue, or it gets answered a second time.
+ */
+static void nl_export_singly(struct export_req *reqs, int start, int end)
+{
+ int i;
+
+ for (i = start; i < end; i++)
+ if (nl_export_one(reqs[i].client, reqs[i].path) == EXPORT_RETRY)
+ nl_delay_export(reqs[i].client, reqs[i].path);
+ else
+ delayed_export_remove(reqs[i].client, reqs[i].path);
}
static void cache_nl_process_export(void)
{
struct export_req *reqs = NULL;
int nreqs = 0;
- struct nl_msg *msg;
- int i;
+ int i = 0;
/* Fetch all pending requests from the kernel */
if (cache_nl_get_export_reqs(&reqs, &nreqs)) {
@@ -1863,29 +2067,46 @@ static void cache_nl_process_export(void)
xlog(D_CALL, "cache_nl_process_export: %d pending export requests", nreqs);
- /* Build the SET_REQS response */
- msg = cache_nl_new_msg(nfsd_nl_family,
- NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
- if (!msg)
- goto out_free;
+ while (i < nreqs) {
+ int start = i;
+ struct nl_msg *msg;
- for (i = 0; i < nreqs; i++) {
- switch (nl_add_export_req(&msg, reqs[i].client, reqs[i].path)) {
- case EXPORT_ANSWERED:
- break;
- case EXPORT_RETRY:
- nl_delay_export(reqs[i].client, reqs[i].path);
+ msg = cache_nl_new_msg(nfsd_nl_family,
+ NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
+ if (!msg)
break;
- case EXPORT_NOMEM:
- goto out_free;
+
+ for (; i < nreqs; i++) {
+ enum export_result res;
+
+ res = nl_add_export_req(msg, reqs[i].client,
+ reqs[i].path, NULL);
+ if (res == EXPORT_FULL)
+ break;
+ if (res == EXPORT_RETRY)
+ nl_delay_export(reqs[i].client, reqs[i].path);
}
- }
- if (nl_msg_has_reqs(msg))
- cache_nl_set_reqs(nfsd_nl_cmd_sock, msg);
- nlmsg_free(msg);
+ /*
+ * One bad entry fails the whole message, so resubmit the
+ * batch singly to find out which and answer the rest.
+ */
+ if (nl_msg_has_reqs(msg) &&
+ cache_nl_set_reqs(nfsd_nl_cmd_sock, msg, NULL) < 0) {
+ xlog(D_CALL, "%s: batch refused, answering %d request%s"
+ " singly", __func__, i - start,
+ i - start == 1 ? "" : "s");
+ nl_export_singly(reqs, start, i);
+ }
+ nlmsg_free(msg);
+
+ /* First entry did not fit an empty message: answer it alone */
+ if (i == start) {
+ nl_export_singly(reqs, i, i + 1);
+ i++;
+ }
+ }
-out_free:
for (i = 0; i < nreqs; i++) {
free(reqs[i].client);
free(reqs[i].path);
@@ -2155,7 +2376,7 @@ static void cache_nl_process_expkey(void)
do_add_expkey:
if (nfsd_nl_add_expkey(msg, dom, fsidtype, fsid,
fsidlen, found_path) < 0) {
- cache_nl_set_reqs(nfsd_nl_cmd_sock, msg);
+ cache_nl_set_reqs(nfsd_nl_cmd_sock, msg, NULL);
nlmsg_free(msg);
msg = cache_nl_new_msg(nfsd_nl_family,
NFSD_CMD_EXPKEY_SET_REQS, 0);
@@ -2176,7 +2397,7 @@ do_add_expkey:
nfs_freeaddrinfo(ai);
}
- cache_nl_set_reqs(nfsd_nl_cmd_sock, msg);
+ cache_nl_set_reqs(nfsd_nl_cmd_sock, msg, NULL);
nlmsg_free(msg);
out_free:
@@ -2466,7 +2687,7 @@ static void cache_nl_process_ip_map(void)
}
if (nl_add_ip_map(msg, class, ipaddr, domain) < 0) {
- cache_nl_set_reqs(sunrpc_nl_cmd_sock, msg);
+ cache_nl_flush_reqs(sunrpc_nl_cmd_sock, msg, "ip_map");
nlmsg_free(msg);
msg = cache_nl_new_msg(sunrpc_nl_family,
SUNRPC_CMD_IP_MAP_SET_REQS, 0);
@@ -2496,7 +2717,7 @@ static void cache_nl_process_ip_map(void)
nfs_freeaddrinfo(tmp);
}
- cache_nl_set_reqs(sunrpc_nl_cmd_sock, msg);
+ cache_nl_flush_reqs(sunrpc_nl_cmd_sock, msg, "ip_map");
nlmsg_free(msg);
out_free:
@@ -2714,7 +2935,7 @@ static void cache_nl_process_unix_gid(void)
if (ret < 0) {
/* Flush current message and retry with a fresh one */
- cache_nl_set_reqs(sunrpc_nl_cmd_sock, msg);
+ cache_nl_flush_reqs(sunrpc_nl_cmd_sock, msg, "unix_gid");
nlmsg_free(msg);
msg = cache_nl_new_msg(sunrpc_nl_family,
SUNRPC_CMD_UNIX_GID_SET_REQS, 0);
@@ -2731,7 +2952,7 @@ static void cache_nl_process_unix_gid(void)
}
}
- cache_nl_set_reqs(sunrpc_nl_cmd_sock, msg);
+ cache_nl_flush_reqs(sunrpc_nl_cmd_sock, msg, "unix_gid");
nlmsg_free(msg);
out_free:
--
2.55.0
next prev parent reply other threads:[~2026-09-14 13:14 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 13:14 [PATCH nfs-utils v3 00/11] mountd: bugfixes for up/downcall interfaces Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 01/11] mountd: factor out the per-path export attribute computation Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 02/11] mountd: handle unmountable paths and junctions in the netlink downcall Jeff Layton
2026-09-14 13:14 ` Jeff Layton [this message]
2026-09-14 13:14 ` [PATCH nfs-utils v3 04/11] mountd: don't leak the parent export's fsid onto crossmnt submounts Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 05/11] mountd: retry unresolvable fsid lookups on the netlink downcall Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 06/11] mountd: bound the junction path before copying it into e_path Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 07/11] mountd: give each worker its own netlink command socket Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 08/11] mountd: retry export attributes that fail to resolve for a passing reason Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 09/11] mountd: drop a deferred fsid lookup once it has been answered Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 10/11] mountd: bound the retry queues Jeff Layton
2026-09-14 13:14 ` [PATCH nfs-utils v3 11/11] mountd: don't warn about pipefs submounts nobody asked to export Jeff Layton
2026-09-15 9:24 ` [PATCH nfs-utils v3 00/11] mountd: bugfixes for up/downcall interfaces Mantas Mikulėnas
2026-09-17 7:21 ` Steve Dickson
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=20260914-nl-crossmnt-v3-3-a984a6c94829@kernel.org \
--to=jlayton@kernel.org \
--cc=cel@kernel.org \
--cc=grawity@gmail.com \
--cc=linux-nfs@vger.kernel.org \
--cc=steved@redhat.com \
/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