[PATCH nfs-utils 15/17] exportfs: add netlink support for cache flush with /proc fallback

Jeff Layton
2026-03-16T15:17:09Z

Move cache_flush() from support/nfs/cacheio.c to a new file
support/export/cache_flush.c, which has access to libnl.

The new implementation tries netlink first:
- Sends SUNRPC_CMD_CACHE_FLUSH to flush auth.unix.ip and auth.unix.gid
- Sends NFSD_CMD_CACHE_FLUSH to flush nfsd.fh and nfsd.export
- Flushes sunrpc caches before nfsd caches to maintain dependency order

If the sunrpc genl family cannot be resolved (older kernel), falls back
to the original /proc/net/rpc/*/flush write path.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
support/export/Makefile.am | 2 +-
support/export/cache_flush.c | 164 +++++++++++++++++++++++++++++++++++++++++++
support/nfs/cacheio.c | 49 +------------
3 files changed, 166 insertions(+), 49 deletions(-)

diff --git a/support/export/Makefile.am b/support/export/Makefile.am
index ae7ace44112b889f1c461c5473fb1bd42a42f182..33416176c41eb6eadd1880f7c7432b4ca2d8c973 100644
--- a/support/export/Makefile.am
+++ b/support/export/Makefile.am
@@ -12,7 +12,7 @@ EXTRA_DIST = mount.x
noinst_LIBRARIES = libexport.a
libexport_a_SOURCES = client.c export.c hostname.c \
xtab.c mount_clnt.c mount_xdr.c \
- cache.c auth.c v4root.c fsloc.c \
+ cache.c cache_flush.c auth.c v4root.c fsloc.c \
v4clients.c
libexport_a_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport \
$(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS)
diff --git a/support/export/cache_flush.c b/support/export/cache_flush.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d7f12b212967e5b3d1a2357de07bc3ba5f0b674
--- /dev/null
+++ b/support/export/cache_flush.c
@@ -0,0 +1,164 @@
+/*
+ * support/export/cache_flush.c
+ *
+ * Flush knfsd caches via netlink with /proc fallback.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "nfslib.h"
+#include "xlog.h"
+
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/msg.h>
+
+#ifdef USE_SYSTEM_NFSD_NETLINK_H
+#include <linux/nfsd_netlink.h>
+#else
+#include "nfsd_netlink.h"
+#endif
+
+#ifdef USE_SYSTEM_SUNRPC_NETLINK_H
+#include <linux/sunrpc_netlink.h>
+#else
+#include "sunrpc_netlink.h"
+#endif
+
+static int nl_send_flush(struct nl_sock *sock, int family, int cmd)
+{
+ struct nl_msg *msg;
+ int ret;
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ if (!genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family,
+ 0, 0, cmd, 0)) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ /* No mask attribute = flush all caches in this family */
+ ret = nl_send_auto(sock, msg);
+ if (ret >= 0)
+ ret = nl_wait_for_ack(sock);
+ nlmsg_free(msg);
+ return ret;
+}
+
+static int cache_nl_flush(void)
+{
+ struct nl_sock *sock;
+ int family, ret, val = 1;
+
+ sock = nl_socket_alloc();
+ if (!sock)
+ return -1;
+
+ if (genl_connect(sock)) {
+ nl_socket_free(sock);
+ return -1;
+ }
+
+ setsockopt(nl_socket_get_fd(sock), SOL_NETLINK, NETLINK_EXT_ACK,
+ &val, sizeof(val));
+
+ /* Flush sunrpc caches first (dependency order) */
+ family = genl_ctrl_resolve(sock, SUNRPC_FAMILY_NAME);
+ if (family < 0) {
+ xlog(D_NETLINK, "sunrpc genl family not found, "
+ "skipping netlink flush");
+ nl_socket_free(sock);
+ return -1;
+ }
+
+ ret = nl_send_flush(sock, family, SUNRPC_CMD_CACHE_FLUSH);
+ if (ret < 0) {
+ xlog(D_NETLINK, "sunrpc cache flush failed: %d", ret);
+ nl_socket_free(sock);
+ return -1;
+ }
+
+ /* Flush nfsd caches */
+ family = genl_ctrl_resolve(sock, NFSD_FAMILY_NAME);
+ if (family < 0) {
+ xlog(D_NETLINK, "nfsd genl family not found, "
+ "skipping nfsd cache flush");
+ nl_socket_free(sock);
+ return 0;
+ }
+
+ ret = nl_send_flush(sock, family, NFSD_CMD_CACHE_FLUSH);
+ nl_socket_free(sock);
+ if (ret < 0) {
+ xlog(D_NETLINK, "nfsd cache flush failed: %d", ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void cache_proc_flush(void)
+{
+ int c;
+ char stime[32];
+ char path[200];
+ time_t now;
+ /* Note: the order of these caches is important.
+ * They need to be flushed in dependency order. So
+ * a cache that references items in another cache,
+ * as nfsd.fh entries reference items in nfsd.export,
+ * must be flushed before the cache that it references.
+ */
+ static char *cachelist[] = {
+ "auth.unix.ip",
+ "auth.unix.gid",
+ "nfsd.fh",
+ "nfsd.export",
+ NULL
+ };
+ now = time(0);
+
+ /* Since v4.16-rc2-3-g3b68e6ee3cbd the timestamp written is ignored.
+ * It is safest always to flush caches if there is any doubt.
+ * For earlier kernels, writing the next second from now is
+ * the best we can do.
+ */
+ sprintf(stime, "%" PRId64 "\n", (int64_t)now+1);
+ for (c=0; cachelist[c]; c++) {
+ int fd;
+ sprintf(path, "/proc/net/rpc/%s/flush", cachelist[c]);
+ fd = open(path, O_RDWR);
+ if (fd >= 0) {
+ if (write(fd, stime, strlen(stime)) != (ssize_t)strlen(stime)) {
+ xlog_warn("Writing to '%s' failed: errno %d (%s)",
+ path, errno, strerror(errno));
+ }
+ close(fd);
+ }
+ }
+}
+
+void
+cache_flush(void)
+{
+ if (cache_nl_flush() == 0) {
+ xlog(D_NETLINK, "cache flush via netlink succeeded");
+ return;
+ }
+ /* Fallback: /proc path */
+ cache_proc_flush();
+}
diff --git a/support/nfs/cacheio.c b/support/nfs/cacheio.c
index bd4da0e5ad93f963bf8090062541f34ededd1e03..95eeabbdb978cd227da9877b81e5ed2d65dedaf9 100644
--- a/support/nfs/cacheio.c
+++ b/support/nfs/cacheio.c
@@ -203,51 +203,4 @@ int qword_get_uint(char **bpp, unsigned int *anint)
return 0;
}

-/* flush the kNFSd caches.
- * Set the flush time to the mtime of the etab state file or
- * if force, to now.
- * the caches to flush are:
- * auth.unix.ip nfsd.export nfsd.fh
- */
-
-void
-cache_flush(void)
-{
- int c;
- char stime[32];
- char path[200];
- time_t now;
- /* Note: the order of these caches is important.
- * They need to be flushed in dependancy order. So
- * a cache that references items in another cache,
- * as nfsd.fh entries reference items in nfsd.export,
- * must be flushed before the cache that it references.
- */
- static char *cachelist[] = {
- "auth.unix.ip",
- "auth.unix.gid",
- "nfsd.fh",
- "nfsd.export",
- NULL
- };
- now = time(0);
-
- /* Since v4.16-rc2-3-g3b68e6ee3cbd the timestamp written is ignored.
- * It is safest always to flush caches if there is any doubt.
- * For earlier kernels, writing the next second from now is
- * the best we can do.
- */
- sprintf(stime, "%" PRId64 "\n", (int64_t)now+1);
- for (c=0; cachelist[c]; c++) {
- int fd;
- sprintf(path, "/proc/net/rpc/%s/flush", cachelist[c]);
- fd = open(path, O_RDWR);
- if (fd >= 0) {
- if (write(fd, stime, strlen(stime)) != (ssize_t)strlen(stime)) {
- xlog_warn("Writing to '%s' failed: errno %d (%s)",
- path, errno, strerror(errno));
- }
- close(fd);
- }
- }
-}
+/* cache_flush() has moved to support/export/cache.c for netlink support */

--
2.53.0