public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Steve Dickson <steved@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/nfs-utils] rawhide: Updated to the latest RC release: nfs-utils-2-9-2-rc4
Date: Sun, 31 May 2026 12:23:20 GMT	[thread overview]
Message-ID: <178023020042.1.6044723889829551561.rpms-nfs-utils-1a9c20bc3cc9@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/nfs-utils
            Branch : rawhide
            Commit : 1a9c20bc3cc9b8597cadddca241d4a310827fbd8
            Author : Steve Dickson <steved@redhat.com>
            Date   : 2026-05-31T08:22:11-04:00
            Stats  : +1142/-716 in 3 file(s)
            URL    : https://src.fedoraproject.org/rpms/nfs-utils/c/1a9c20bc3cc9b8597cadddca241d4a310827fbd8?branch=rawhide

            Log:
            Updated to the latest RC release: nfs-utils-2-9-2-rc4

Signed-off-by: Steve Dickson <steved@redhat.com>

---
diff --git a/nfs-utils-2.9.2-rc3.patch b/nfs-utils-2.9.2-rc3.patch
deleted file mode 100644
index 3e1e005..0000000
--- a/nfs-utils-2.9.2-rc3.patch
+++ /dev/null
@@ -1,714 +0,0 @@
-diff --git a/configure.ac b/configure.ac
-index 115c611f..8ca06fd6 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -256,6 +256,8 @@ PKG_CHECK_MODULES(LIBNL3, libnl-3.0 >= 3.1)
- PKG_CHECK_MODULES(LIBNLGENL3, libnl-genl-3.0 >= 3.1)
- 
- AC_CHECK_HEADERS(linux/nfsd_netlink.h)
-+AC_DEFINE([HAVE_NFSD_NETLINK], 1,
-+	  [Define to 1 if nfsd generic netlink support is available])
- 
- # ensure we have the expkey attributes
- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <linux/nfsd_netlink.h>]],
-diff --git a/support/export/cache.c b/support/export/cache.c
-index 2f128d7d..65008f51 100644
---- a/support/export/cache.c
-+++ b/support/export/cache.c
-@@ -40,7 +40,7 @@
- #include <netlink/genl/ctrl.h>
- #include <netlink/msg.h>
- #include <netlink/attr.h>
--#include <linux/netlink.h>
-+#include "compat.h"
- 
- #ifdef USE_SYSTEM_NFSD_NETLINK_H
- #include <linux/nfsd_netlink.h>
-diff --git a/support/export/cache_flush.c b/support/export/cache_flush.c
-index ed7b964f..2a24dec7 100644
---- a/support/export/cache_flush.c
-+++ b/support/export/cache_flush.c
-@@ -38,6 +38,8 @@ extern int no_netlink;
- #include "sunrpc_netlink.h"
- #endif
- 
-+#include "compat.h"
-+
- static int nl_send_flush(struct nl_sock *sock, int family, int cmd)
- {
- 	struct nl_msg *msg;
-diff --git a/support/export/xtab.c b/support/export/xtab.c
-index 282f15bc..0a966051 100644
---- a/support/export/xtab.c
-+++ b/support/export/xtab.c
-@@ -33,11 +33,8 @@ int v4root_needed;
- static void cond_rename(char *newfile, char *oldfile);
- 
- static int
--xtab_read(char *xtab, char *lockfn, int is_export)
-+xtab_read(char *xtab, char *lockfn)
- {
--    /* is_export == 0  => reading /proc/fs/nfs/exports - we know these things are exported to kernel
--     * is_export == 1  => reading /var/lib/nfs/etab - these things are allowed to be exported
--     */
- 	struct exportent	*xp;
- 	nfs_export		*exp;
- 	int			lockid;
-@@ -45,11 +42,10 @@ xtab_read(char *xtab, char *lockfn, int is_export)
- 	if ((lockid = xflock(lockfn, "r")) < 0)
- 		return 0;
- 	setexportent(xtab, "r");
--	if (is_export == 1)
--		v4root_needed = 1;
--	while ((xp = getexportent(is_export==0)) != NULL) {
--		if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) &&
--		    !(exp = export_create(xp, is_export!=1))) {
-+	v4root_needed = 1;
-+	while ((xp = getexportent(0)) != NULL) {
-+		if (!(exp = export_lookup(xp->e_hostname, xp->e_path, 0)) &&
-+		    !(exp = export_create(xp, 0))) {
-                         if(xp->e_hostname) {
-                             free(xp->e_hostname);
-                             xp->e_hostname=NULL;
-@@ -60,17 +56,10 @@ xtab_read(char *xtab, char *lockfn, int is_export)
-                         }
- 			continue;
- 		}
--		switch (is_export) {
--		case 0:
--			exp->m_exported = 1;
--			break;
--		case 1:
--			exp->m_xtabent = 1;
--			exp->m_mayexport = 1;
--			if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0)
--				v4root_needed = 0;
--			break;
--		}  
-+		exp->m_xtabent = 1;
-+		exp->m_mayexport = 1;
-+		if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0)
-+			v4root_needed = 0;
-                 if(xp->e_hostname) {
-                     free(xp->e_hostname);
-                     xp->e_hostname=NULL;
-@@ -90,7 +79,7 @@ xtab_read(char *xtab, char *lockfn, int is_export)
- int
- xtab_export_read(void)
- {
--	return xtab_read(etab.statefn, etab.lockfn, 1);
-+	return xtab_read(etab.statefn, etab.lockfn);
- }
- 
- /*
-@@ -100,7 +89,7 @@ xtab_export_read(void)
-  * fix the auth_reload logic as well...
-  */
- static int
--xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
-+xtab_write(char *xtab, char *xtabtmp, char *lockfn)
- {
- 	struct exportent	xe;
- 	nfs_export		*exp;
-@@ -114,9 +103,7 @@ xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
- 
- 	for (i = 0; i < MCL_MAXTYPES; i++) {
- 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
--			if (is_export && !exp->m_xtabent)
--				continue;
--			if (!is_export && ! exp->m_exported)
-+			if (!exp->m_xtabent)
- 				continue;
- 
- 			/* write out the export entry using the FQDN */
-@@ -137,7 +124,7 @@ xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
- int
- xtab_export_write(void)
- {
--	return xtab_write(etab.statefn, etab.tmpfn, etab.lockfn, 1);
-+	return xtab_write(etab.statefn, etab.tmpfn, etab.lockfn);
- }
- 
- /*
-diff --git a/support/include/compat.h b/support/include/compat.h
-new file mode 100644
-index 00000000..83229b65
---- /dev/null
-+++ b/support/include/compat.h
-@@ -0,0 +1,10 @@
-+#ifndef COMPAT_H
-+#define COMPAT_H
-+
-+#include <linux/netlink.h>
-+
-+#ifndef NETLINK_EXT_ACK
-+#define NETLINK_EXT_ACK 11
-+#endif
-+
-+#endif /* COMPAT_H */
-diff --git a/support/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
-index 2d708d24..a6a83186 100644
---- a/support/include/nfsd_netlink.h
-+++ b/support/include/nfsd_netlink.h
-@@ -128,6 +128,27 @@ enum {
- 	NFSD_A_POOL_MODE_MAX = (__NFSD_A_POOL_MODE_MAX - 1)
- };
- 
-+enum {
-+	NFSD_A_UNLOCK_IP_ADDRESS = 1,
-+
-+	__NFSD_A_UNLOCK_IP_MAX,
-+	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
-+};
-+
-+enum {
-+	NFSD_A_UNLOCK_FILESYSTEM_PATH = 1,
-+
-+	__NFSD_A_UNLOCK_FILESYSTEM_MAX,
-+	NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
-+};
-+
-+enum {
-+	NFSD_A_UNLOCK_EXPORT_PATH = 1,
-+
-+	__NFSD_A_UNLOCK_EXPORT_MAX,
-+	NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1)
-+};
-+
- enum {
- 	NFSD_A_FSLOCATION_HOST = 1,
- 	NFSD_A_FSLOCATION_PATH,
-@@ -229,6 +250,9 @@ enum {
- 	NFSD_CMD_EXPKEY_GET_REQS,
- 	NFSD_CMD_EXPKEY_SET_REQS,
- 	NFSD_CMD_CACHE_FLUSH,
-+	NFSD_CMD_UNLOCK_IP,
-+	NFSD_CMD_UNLOCK_FILESYSTEM,
-+	NFSD_CMD_UNLOCK_EXPORT,
- 
- 	__NFSD_CMD_MAX,
- 	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
-diff --git a/support/include/nfsdnl.h b/support/include/nfsdnl.h
-new file mode 100644
-index 00000000..352801e5
---- /dev/null
-+++ b/support/include/nfsdnl.h
-@@ -0,0 +1,34 @@
-+/*
-+ * Helper for sending nfsd generic netlink commands.
-+ *
-+ * Used by both nfsdctl and exportfs.
-+ */
-+
-+#ifndef NFS_UTILS_NFSDNL_H
-+#define NFS_UTILS_NFSDNL_H
-+
-+#ifdef HAVE_NFSD_NETLINK
-+
-+/**
-+ * nfsd_nl_cmd_str - send an nfsd netlink command carrying a string attribute
-+ * @cmd:   NFSD_CMD_* command number
-+ * @attr:  NFSD_A_* attribute number
-+ * @value: NUL-terminated string value for the attribute
-+ *
-+ * Opens a genetlink connection, resolves the "nfsd" family, sends a
-+ * single "do" command with one string attribute, waits for the ACK,
-+ * and cleans up.
-+ *
-+ * Returns 0 on success or a negative errno on failure.
-+ */
-+int nfsd_nl_cmd_str(int cmd, int attr, const char *value);
-+
-+#else
-+
-+static inline int nfsd_nl_cmd_str(int cmd, int attr, const char *value)
-+{
-+	return -ENOSYS;
-+}
-+
-+#endif /* HAVE_NFSD_NETLINK */
-+#endif /* NFS_UTILS_NFSDNL_H */
-diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
-index 5bfd71a9..64ad5d07 100644
---- a/support/nfs/Makefile.am
-+++ b/support/nfs/Makefile.am
-@@ -11,6 +11,13 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
- libnfs_la_LIBADD = libnfsconf.la -luuid
- libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
- 
-+if CONFIG_NFSDCTL
-+libnfs_la_SOURCES += nfsdnl.c
-+libnfs_la_CPPFLAGS += $(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS) \
-+		      -I$(top_srcdir)/utils/nfsdctl
-+libnfs_la_LIBADD += $(LIBNL3_LIBS) $(LIBNLGENL3_LIBS)
-+endif
-+
- libnfsconf_la_SOURCES = conffile.c xlog.c
- 
- MAINTAINERCLEANFILES = Makefile.in
-diff --git a/support/nfs/fh_key_file.c b/support/nfs/fh_key_file.c
-index 5f5eafc1..81ea1500 100644
---- a/support/nfs/fh_key_file.c
-+++ b/support/nfs/fh_key_file.c
-@@ -26,6 +26,7 @@
- #include <sys/types.h>
- #include <unistd.h>
- #include <errno.h>
-+#include <string.h>
- #include <uuid/uuid.h>
- 
- #include "nfslib.h"
-diff --git a/support/nfs/nfsdnl.c b/support/nfs/nfsdnl.c
-new file mode 100644
-index 00000000..ece0b57a
---- /dev/null
-+++ b/support/nfs/nfsdnl.c
-@@ -0,0 +1,124 @@
-+/*
-+ * nfsdnl.c -- send nfsd generic netlink commands
-+ *
-+ * Helper shared by nfsdctl and exportfs.
-+ */
-+
-+#ifdef HAVE_CONFIG_H
-+#include <config.h>
-+#endif
-+
-+#include <errno.h>
-+#include <string.h>
-+
-+#include <netlink/genl/genl.h>
-+#include <netlink/genl/ctrl.h>
-+#include <netlink/msg.h>
-+#include <netlink/attr.h>
-+
-+#include "xlog.h"
-+#include "nfsdnl.h"
-+
-+#ifdef USE_SYSTEM_NFSD_NETLINK_H
-+#include <linux/nfsd_netlink.h>
-+#else
-+#include "nfsd_netlink.h"
-+#endif
-+
-+#define NFSDNL_BUFSIZE	(4096)
-+
-+static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
-+			 void *arg)
-+{
-+	int *ret = arg;
-+	*ret = err->error;
-+	return NL_STOP;
-+}
-+
-+static int finish_handler(struct nl_msg *msg, void *arg)
-+{
-+	int *ret = arg;
-+	*ret = 0;
-+	return NL_SKIP;
-+}
-+
-+static int ack_handler(struct nl_msg *msg __attribute__((unused)),
-+		       void *arg)
-+{
-+	int *ret = arg;
-+	*ret = 0;
-+	return NL_STOP;
-+}
-+
-+/**
-+ * nfsd_nl_cmd_str - send an nfsd netlink command carrying a string attribute
-+ * @cmd:   NFSD_CMD_* command number
-+ * @attr:  NFSD_A_* attribute number
-+ * @value: NUL-terminated string value for the attribute
-+ *
-+ * Returns 0 on success or a negative errno on failure.
-+ */
-+int nfsd_nl_cmd_str(int cmd, int attr, const char *value)
-+{
-+	struct genlmsghdr *ghdr;
-+	struct nl_sock *sock;
-+	struct nl_msg *msg;
-+	struct nl_cb *cb;
-+	int family;
-+	int ret;
-+
-+	sock = nl_socket_alloc();
-+	if (!sock)
-+		return -ENOMEM;
-+	if (genl_connect(sock)) {
-+		ret = -ECONNREFUSED;
-+		goto out_sock;
-+	}
-+	nl_socket_set_buffer_size(sock, NFSDNL_BUFSIZE, NFSDNL_BUFSIZE);
-+
-+	family = genl_ctrl_resolve(sock, NFSD_FAMILY_NAME);
-+	if (family < 0) {
-+		ret = family;
-+		goto out_sock;
-+	}
-+
-+	msg = nlmsg_alloc();
-+	if (!msg) {
-+		ret = -ENOMEM;
-+		goto out_sock;
-+	}
-+	if (!genlmsg_put(msg, 0, 0, family, 0, 0, 0, 0)) {
-+		ret = -ENOMEM;
-+		goto out_msg;
-+	}
-+
-+	ghdr = nlmsg_data(nlmsg_hdr(msg));
-+	ghdr->cmd = (__u8)cmd;
-+	nla_put_string(msg, attr, value);
-+
-+	cb = nl_cb_alloc(NL_CB_CUSTOM);
-+	if (!cb) {
-+		ret = -ENOMEM;
-+		goto out_msg;
-+	}
-+
-+	ret = nl_send_auto(sock, msg);
-+	if (ret < 0)
-+		goto out_cb;
-+
-+	ret = 1;
-+	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
-+	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret);
-+	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
-+
-+	while (ret > 0)
-+		nl_recvmsgs(sock, cb);
-+
-+out_cb:
-+	nl_cb_put(cb);
-+out_msg:
-+	nlmsg_free(msg);
-+out_sock:
-+	nl_socket_free(sock);
-+	return ret;
-+}
-diff --git a/support/reexport/backend_sqlite.c b/support/reexport/backend_sqlite.c
-index 0eb5ea37..a1e981e4 100644
---- a/support/reexport/backend_sqlite.c
-+++ b/support/reexport/backend_sqlite.c
-@@ -9,6 +9,8 @@
- #include <string.h>
- #include <unistd.h>
- 
-+#include <sys/syscall.h>
-+
- #ifdef HAVE_GETRANDOM
- # include <sys/random.h>
- # if !defined(SYS_getrandom) && defined(__NR_getrandom)
-diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py
-index 69d24a11..af04aac5 100755
---- a/tools/nfs-iostat/nfs-iostat.py
-+++ b/tools/nfs-iostat/nfs-iostat.py
-@@ -327,7 +327,7 @@ class DeviceData:
-             print()
-             print('%d congestion waits' % congestionwaits)
- 
--    def __print_rpc_op_stats(self, op, sample_time):
-+    def __print_rpc_op_stats(self, op, sample_time, use_mb=False):
-         """Print generic stats for one RPC op
-         """
-         if op not in self.__rpc_data:
-@@ -343,9 +343,19 @@ class DeviceData:
-         if len(rpc_stats) >= 9:
-             errs = float(rpc_stats[8])
- 
-+        # scale to MB if requested
-+        if use_mb:
-+            throughput = kilobytes / 1024.0
-+            throughput_label = 'MB/s'
-+            per_op_label = 'MB/op'
-+        else:
-+            throughput = kilobytes
-+            throughput_label = 'kB/s'
-+            per_op_label = 'kB/op'
-+
-         # prevent floating point exceptions
-         if ops != 0:
--            kb_per_op = kilobytes / ops
-+            unit_per_op = throughput / ops
-             retrans_percent = (retrans * 100) / ops
-             rtt_per_op = rtt / ops
-             exe_per_op = exe / ops
-@@ -353,7 +363,7 @@ class DeviceData:
-             if len(rpc_stats) >= 9:
-                 errs_percent = (errs * 100) / ops
-         else:
--            kb_per_op = 0.0
-+            unit_per_op = 0.0
-             retrans_percent = 0.0
-             rtt_per_op = 0.0
-             exe_per_op = 0.0
-@@ -364,8 +374,8 @@ class DeviceData:
-         op += ':'
-         print(format(op.lower(), '<16s'), end='')
-         print(format('ops/s', '>8s'), end='')
--        print(format('kB/s', '>16s'), end='')
--        print(format('kB/op', '>16s'), end='')
-+        print(format(throughput_label, '>16s'), end='')
-+        print(format(per_op_label, '>16s'), end='')
-         print(format('retrans', '>16s'), end='')
-         print(format('avg RTT (ms)', '>16s'), end='')
-         print(format('avg exe (ms)', '>16s'), end='')
-@@ -375,8 +385,8 @@ class DeviceData:
-         print()
- 
-         print(format((ops / sample_time), '>24.3f'), end='')
--        print(format((kilobytes / sample_time), '>16.3f'), end='')
--        print(format(kb_per_op, '>16.3f'), end='')
-+        print(format((throughput / sample_time), '>16.3f'), end='')
-+        print(format(unit_per_op, '>16.3f'), end='')
-         retransmits = '{0:>10.0f} ({1:>3.1f}%)'.format(retrans, retrans_percent).strip()
-         print(format(retransmits, '>16'), end='')
-         print(format(rtt_per_op, '>16.3f'), end='')
-@@ -395,9 +405,11 @@ class DeviceData:
-             sample_time = 1;
-         return (sends / sample_time)
- 
--    def display_iostats(self, sample_time, which):
-+    def display_iostats(self, sample_time, options):
-         """Display NFS and RPC stats in an iostat-like way
-         """
-+        which = options.which
-+        use_mb = options.megabytes
-         sends = float(self.__rpc_data['rpcsends'])
-         if sample_time == 0:
-             sample_time = float(self.__nfs_data['age'])
-@@ -423,21 +435,21 @@ class DeviceData:
-         print()
- 
-         if which == 0:
--            self.__print_rpc_op_stats('READ', sample_time)
--            self.__print_rpc_op_stats('WRITE', sample_time)
-+            self.__print_rpc_op_stats('READ', sample_time, use_mb)
-+            self.__print_rpc_op_stats('WRITE', sample_time, use_mb)
-         elif which == 1:
--            self.__print_rpc_op_stats('GETATTR', sample_time)
--            self.__print_rpc_op_stats('ACCESS', sample_time)
-+            self.__print_rpc_op_stats('GETATTR', sample_time, use_mb)
-+            self.__print_rpc_op_stats('ACCESS', sample_time, use_mb)
-             self.__print_attr_cache_stats(sample_time)
-         elif which == 2:
--            self.__print_rpc_op_stats('LOOKUP', sample_time)
--            self.__print_rpc_op_stats('READDIR', sample_time)
-+            self.__print_rpc_op_stats('LOOKUP', sample_time, use_mb)
-+            self.__print_rpc_op_stats('READDIR', sample_time, use_mb)
-             if 'READDIRPLUS' in self.__rpc_data:
--                self.__print_rpc_op_stats('READDIRPLUS', sample_time)
-+                self.__print_rpc_op_stats('READDIRPLUS', sample_time, use_mb)
-             self.__print_dir_cache_stats(sample_time)
-         elif which == 3:
--            self.__print_rpc_op_stats('READ', sample_time)
--            self.__print_rpc_op_stats('WRITE', sample_time)
-+            self.__print_rpc_op_stats('READ', sample_time, use_mb)
-+            self.__print_rpc_op_stats('WRITE', sample_time, use_mb)
-             self.__print_page_stats(sample_time)
- 
-         sys.stdout.flush()
-@@ -500,7 +512,7 @@ def print_iostat_summary(old, new, devices, time, options):
- 
-     count = 1
-     for device in devices:
--        display_stats[device].display_iostats(time, options.which)
-+        display_stats[device].display_iostats(time, options)
- 
-         count += 1
-         if (count > options.list):
-@@ -585,6 +597,11 @@ client are listed.
-                             type="int",
-                             dest="list",
-                             help="only print stats for first LIST mount points")
-+    displaygroup.add_option('-m', '--megabytes',
-+                            action="store_true",
-+                            dest="megabytes",
-+                            default=False,
-+                            help="display throughput in megabytes per second (MB/s) instead of kilobytes per second (kB/s)")
-     parser.add_option_group(displaygroup)
- 
-     (options, args) = parser.parse_args(sys.argv)
-diff --git a/tools/nfs-iostat/nfsiostat.man b/tools/nfs-iostat/nfsiostat.man
-index 104c7ab4..4f24318d 100644
---- a/tools/nfs-iostat/nfsiostat.man
-+++ b/tools/nfs-iostat/nfsiostat.man
-@@ -56,16 +56,16 @@ This is the length of the backlog queue.
- .RE
- .RE
- .RS 8
--- \fBkB/s\fR
-+- \fBkB/s (MB/s)\fR
- .RS
--This is the number of kB written/read per second.
-+This is the number of kB (or MB) written/read per second.
- .RE
- .RE
- .RE
- .RS 8
--- \fBkB/op\fR
-+- \fBkB/op (MB/op)\fR
- .RS
--This is the number of kB written/read per each operation.
-+This is the number of kB (or MB) written/read per each operation.
- .RE
- .RE
- .RE
-@@ -122,6 +122,9 @@ shows help message and exit
- .B \-l LIST or " \-\-list=LIST 
- only print stats for first LIST mount points
- .TP
-+.B \-m or " \-\-megabytes
-+display throughput in megabytes per second
-+.TP
- .B \-p " or " \-\-page
- displays statistics related to the page cache
- .TP
-diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
-index 93f0bcd7..768d2db7 100644
---- a/utils/exportfs/exportfs.c
-+++ b/utils/exportfs/exportfs.c
-@@ -39,6 +39,15 @@
- #include "xlog.h"
- #include "conffile.h"
- #include "reexport.h"
-+#include "nfsdnl.h"
-+
-+#ifdef HAVE_NFSD_NETLINK
-+#ifdef USE_SYSTEM_NFSD_NETLINK_H
-+#include <linux/nfsd_netlink.h>
-+#else
-+#include "nfsd_netlink.h"
-+#endif
-+#endif
- 
- #include <netlink/genl/genl.h>
- #include <netlink/genl/ctrl.h>
-@@ -63,6 +72,7 @@ static void release_lockfile(void);
- 
- static const char *lockfile = EXP_LOCKFILE;
- static int _lockfd = -1;
-+static int f_unexport_all;
- 
- /*
-  * If we aren't careful, changes made by exportfs can be lost
-@@ -246,7 +256,8 @@ main(int argc, char **argv)
- 	 * don't care about what should be exported, as that
- 	 * may require DNS lookups..
- 	 */
--	if (! ( !f_export && f_all)) {
-+	f_unexport_all = !f_export && f_all;
-+	if (!f_unexport_all) {
- 		/* note: xtab_*_read does not update entries if they already exist,
- 		 * so this will not lose new options
- 		 */
-@@ -380,6 +391,26 @@ exportfs(char *arg, char *options, int verbose)
- 		xlog(L_ERROR, "Invalid export syntax: %s", arg);
- }
- 
-+/*
-+ * Check whether any active export remains for the given path across
-+ * all client types.  Returns true if at least one export still has
-+ * m_xtabent set.
-+ */
-+static int
-+path_still_exported(const char *path, size_t nlen)
-+{
-+	nfs_export *exp;
-+	int i;
-+
-+	for (i = 0; i < MCL_MAXTYPES; i++)
-+		for (exp = exportlist[i].p_head; exp; exp = exp->m_next)
-+			if (exp->m_xtabent &&
-+			    strlen(exp->m_export.e_path) == nlen &&
-+			    strncmp(path, exp->m_export.e_path, nlen) == 0)
-+				return 1;
-+	return 0;
-+}
-+
- static void
- unexportfs_parsed(char *hname, char *path, int verbose)
- {
-@@ -434,9 +465,39 @@ unexportfs_parsed(char *hname, char *path, int verbose)
- 		exp->m_mayexport = 0;
- 		success = 1;
- 	}
--	if (!success)
-+	if (!success) {
- 		xlog(L_ERROR, "Could not find '%s:%s' to unexport.", hname, path);
-+		goto out;
-+	}
- 
-+	/*
-+	 * If no exports remain for this path, ask the kernel to
-+	 * revoke any NFSv4 state and close cached file handles
-+	 * associated with exports of this path.  This enables the
-+	 * underlying filesystem to be unmounted.
-+	 *
-+	 * Skip this during "exportfs -ua" -- that is a shutdown
-+	 * operation.  Clients should wait for nfsd to restart and
-+	 * reclaim state through the grace period rather than
-+	 * receiving NFS4ERR_ADMIN_REVOKED.
-+	 */
-+#ifdef HAVE_NFSD_NETLINK
-+	if (!f_unexport_all && !path_still_exported(path, nlen)) {
-+		char pathbuf[NFS_MAXPATHLEN + 1];
-+		int ret;
-+
-+		memcpy(pathbuf, path, nlen);
-+		pathbuf[nlen] = '\0';
-+		ret = nfsd_nl_cmd_str(NFSD_CMD_UNLOCK_EXPORT,
-+				      NFSD_A_UNLOCK_EXPORT_PATH,
-+				      pathbuf);
-+		if (ret && ret != -ENOSYS)
-+			xlog(L_WARNING,
-+			     "Failed to release state for %s: %s",
-+			     pathbuf, strerror(-ret));
-+	}
-+#endif
-+out:
- 	nfs_freeaddrinfo(ai);
- }
- 
-diff --git a/utils/exportfs/exportfs.man b/utils/exportfs/exportfs.man
-index 3737ee81..b5e0c63a 100644
---- a/utils/exportfs/exportfs.man
-+++ b/utils/exportfs/exportfs.man
-@@ -256,6 +256,23 @@ pair. This deletes the specified entry from
- .I /var/lib/nfs/etab
- and removes the corresponding kernel entry (if any).
- .PP
-+When the last client for a given export path is unexported,
-+.B exportfs
-+signals the kernel to revoke NFSv4 state (opens, locks, and
-+delegations) and release cached state for that path.
-+Without this revocation, retained state would prevent the
-+underlying filesystem from being unmounted.
-+Affected clients receive
-+.B NFS4ERR_ADMIN_REVOKED
-+errors for operations that use revoked state.
-+.PP
-+.B "exportfs \-ua"
-+does not revoke NFSv4 state, however.
-+If
-+.B nfsd
-+is then restarted, clients may reclaim state during the
-+grace period.
-+.PP
- .SS Dumping the Export Table
- Invoking
- .B exportfs
-diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
-index 016dd2eb..c7126748 100644
---- a/utils/nfsdctl/nfsdctl.c
-+++ b/utils/nfsdctl/nfsdctl.c
-@@ -26,6 +26,7 @@
- #include <netlink/msg.h>
- #include <netlink/attr.h>
- #include <linux/netlink.h>
-+#include "compat.h"
- 
- #include <readline/readline.h>
- #include <readline/history.h>

diff --git a/nfs-utils-2.9.2-rc4.patch b/nfs-utils-2.9.2-rc4.patch
new file mode 100644
index 0000000..f9dcb51
--- /dev/null
+++ b/nfs-utils-2.9.2-rc4.patch
@@ -0,0 +1,1137 @@
+diff --git a/configure.ac b/configure.ac
+index 115c611f..8ca06fd6 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -256,6 +256,8 @@ PKG_CHECK_MODULES(LIBNL3, libnl-3.0 >= 3.1)
+ PKG_CHECK_MODULES(LIBNLGENL3, libnl-genl-3.0 >= 3.1)
+ 
+ AC_CHECK_HEADERS(linux/nfsd_netlink.h)
++AC_DEFINE([HAVE_NFSD_NETLINK], 1,
++	  [Define to 1 if nfsd generic netlink support is available])
+ 
+ # ensure we have the expkey attributes
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <linux/nfsd_netlink.h>]],
+diff --git a/support/export/cache.c b/support/export/cache.c
+index 2f128d7d..65008f51 100644
+--- a/support/export/cache.c
++++ b/support/export/cache.c
+@@ -40,7 +40,7 @@
+ #include <netlink/genl/ctrl.h>
+ #include <netlink/msg.h>
+ #include <netlink/attr.h>
+-#include <linux/netlink.h>
++#include "compat.h"
+ 
+ #ifdef USE_SYSTEM_NFSD_NETLINK_H
+ #include <linux/nfsd_netlink.h>
+diff --git a/support/export/cache_flush.c b/support/export/cache_flush.c
+index ed7b964f..2a24dec7 100644
+--- a/support/export/cache_flush.c
++++ b/support/export/cache_flush.c
+@@ -38,6 +38,8 @@ extern int no_netlink;
+ #include "sunrpc_netlink.h"
+ #endif
+ 
++#include "compat.h"
++
+ static int nl_send_flush(struct nl_sock *sock, int family, int cmd)
+ {
+ 	struct nl_msg *msg;
+diff --git a/support/export/xtab.c b/support/export/xtab.c
+index 282f15bc..0a966051 100644
+--- a/support/export/xtab.c
++++ b/support/export/xtab.c
+@@ -33,11 +33,8 @@ int v4root_needed;
+ static void cond_rename(char *newfile, char *oldfile);
+ 
+ static int
+-xtab_read(char *xtab, char *lockfn, int is_export)
++xtab_read(char *xtab, char *lockfn)
+ {
+-    /* is_export == 0  => reading /proc/fs/nfs/exports - we know these things are exported to kernel
+-     * is_export == 1  => reading /var/lib/nfs/etab - these things are allowed to be exported
+-     */
+ 	struct exportent	*xp;
+ 	nfs_export		*exp;
+ 	int			lockid;
+@@ -45,11 +42,10 @@ xtab_read(char *xtab, char *lockfn, int is_export)
+ 	if ((lockid = xflock(lockfn, "r")) < 0)
+ 		return 0;
+ 	setexportent(xtab, "r");
+-	if (is_export == 1)
+-		v4root_needed = 1;
+-	while ((xp = getexportent(is_export==0)) != NULL) {
+-		if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) &&
+-		    !(exp = export_create(xp, is_export!=1))) {
++	v4root_needed = 1;
++	while ((xp = getexportent(0)) != NULL) {
++		if (!(exp = export_lookup(xp->e_hostname, xp->e_path, 0)) &&
++		    !(exp = export_create(xp, 0))) {
+                         if(xp->e_hostname) {
+                             free(xp->e_hostname);
+                             xp->e_hostname=NULL;
+@@ -60,17 +56,10 @@ xtab_read(char *xtab, char *lockfn, int is_export)
+                         }
+ 			continue;
+ 		}
+-		switch (is_export) {
+-		case 0:
+-			exp->m_exported = 1;
+-			break;
+-		case 1:
+-			exp->m_xtabent = 1;
+-			exp->m_mayexport = 1;
+-			if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0)
+-				v4root_needed = 0;
+-			break;
+-		}  
++		exp->m_xtabent = 1;
++		exp->m_mayexport = 1;
++		if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0)
++			v4root_needed = 0;
+                 if(xp->e_hostname) {
+                     free(xp->e_hostname);
+                     xp->e_hostname=NULL;
+@@ -90,7 +79,7 @@ xtab_read(char *xtab, char *lockfn, int is_export)
+ int
+ xtab_export_read(void)
+ {
+-	return xtab_read(etab.statefn, etab.lockfn, 1);
++	return xtab_read(etab.statefn, etab.lockfn);
+ }
+ 
+ /*
+@@ -100,7 +89,7 @@ xtab_export_read(void)
+  * fix the auth_reload logic as well...
+  */
+ static int
+-xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
++xtab_write(char *xtab, char *xtabtmp, char *lockfn)
+ {
+ 	struct exportent	xe;
+ 	nfs_export		*exp;
+@@ -114,9 +103,7 @@ xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
+ 
+ 	for (i = 0; i < MCL_MAXTYPES; i++) {
+ 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
+-			if (is_export && !exp->m_xtabent)
+-				continue;
+-			if (!is_export && ! exp->m_exported)
++			if (!exp->m_xtabent)
+ 				continue;
+ 
+ 			/* write out the export entry using the FQDN */
+@@ -137,7 +124,7 @@ xtab_write(char *xtab, char *xtabtmp, char *lockfn, int is_export)
+ int
+ xtab_export_write(void)
+ {
+-	return xtab_write(etab.statefn, etab.tmpfn, etab.lockfn, 1);
++	return xtab_write(etab.statefn, etab.tmpfn, etab.lockfn);
+ }
+ 
+ /*
+diff --git a/support/include/compat.h b/support/include/compat.h
+new file mode 100644
+index 00000000..83229b65
+--- /dev/null
++++ b/support/include/compat.h
+@@ -0,0 +1,10 @@
++#ifndef COMPAT_H
++#define COMPAT_H
++
++#include <linux/netlink.h>
++
++#ifndef NETLINK_EXT_ACK
++#define NETLINK_EXT_ACK 11
++#endif
++
++#endif /* COMPAT_H */
+diff --git a/support/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
+index 2d708d24..3d076d17 100644
+--- a/support/include/nfsd_netlink.h
++++ b/support/include/nfsd_netlink.h
+@@ -151,15 +151,6 @@ enum {
+ 	NFSD_A_AUTH_FLAVOR_MAX = (__NFSD_A_AUTH_FLAVOR_MAX - 1)
+ };
+ 
+-enum {
+-	NFSD_A_SVC_EXPORT_REQ_SEQNO = 1,
+-	NFSD_A_SVC_EXPORT_REQ_CLIENT,
+-	NFSD_A_SVC_EXPORT_REQ_PATH,
+-
+-	__NFSD_A_SVC_EXPORT_REQ_MAX,
+-	NFSD_A_SVC_EXPORT_REQ_MAX = (__NFSD_A_SVC_EXPORT_REQ_MAX - 1)
+-};
+-
+ enum {
+ 	NFSD_A_SVC_EXPORT_SEQNO = 1,
+ 	NFSD_A_SVC_EXPORT_CLIENT,
+@@ -213,6 +204,61 @@ enum {
+ 	NFSD_A_CACHE_FLUSH_MAX = (__NFSD_A_CACHE_FLUSH_MAX - 1)
+ };
+ 
++enum {
++	NFSD_A_UNLOCK_IP_ADDRESS = 1,
++
++	__NFSD_A_UNLOCK_IP_MAX,
++	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
++};
++
++enum {
++	NFSD_A_UNLOCK_FILESYSTEM_PATH = 1,
++
++	__NFSD_A_UNLOCK_FILESYSTEM_MAX,
++	NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
++};
++
++enum {
++	NFSD_A_UNLOCK_EXPORT_PATH = 1,
++
++	__NFSD_A_UNLOCK_EXPORT_MAX,
++	NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1)
++};
++
++enum {
++	NFSD_A_SERVER_PROC_ENTRY_OP = 1,
++	NFSD_A_SERVER_PROC_ENTRY_COUNT,
++	NFSD_A_SERVER_PROC_ENTRY_PAD,
++
++	__NFSD_A_SERVER_PROC_ENTRY_MAX,
++	NFSD_A_SERVER_PROC_ENTRY_MAX = (__NFSD_A_SERVER_PROC_ENTRY_MAX - 1)
++};
++
++enum {
++	NFSD_A_SERVER_STATS_RC_HITS = 1,
++	NFSD_A_SERVER_STATS_RC_MISSES,
++	NFSD_A_SERVER_STATS_RC_NOCACHE,
++	NFSD_A_SERVER_STATS_PAD,
++	NFSD_A_SERVER_STATS_FH_STALE,
++	NFSD_A_SERVER_STATS_IO_READ,
++	NFSD_A_SERVER_STATS_IO_WRITE,
++	NFSD_A_SERVER_STATS_NETCNT,
++	NFSD_A_SERVER_STATS_NETUDPCNT,
++	NFSD_A_SERVER_STATS_NETTCPCNT,
++	NFSD_A_SERVER_STATS_NETTCPCONN,
++	NFSD_A_SERVER_STATS_RPCCNT,
++	NFSD_A_SERVER_STATS_RPCBADFMT,
++	NFSD_A_SERVER_STATS_RPCBADAUTH,
++	NFSD_A_SERVER_STATS_RPCBADCLNT,
++	NFSD_A_SERVER_STATS_PROC2_OPS,
++	NFSD_A_SERVER_STATS_PROC3_OPS,
++	NFSD_A_SERVER_STATS_PROC4_OPS,
++	NFSD_A_SERVER_STATS_PROC4OPS_OPS,
++
++	__NFSD_A_SERVER_STATS_MAX,
++	NFSD_A_SERVER_STATS_MAX = (__NFSD_A_SERVER_STATS_MAX - 1)
++};
++
+ enum {
+ 	NFSD_CMD_RPC_STATUS_GET = 1,
+ 	NFSD_CMD_THREADS_SET,
+@@ -229,6 +275,10 @@ enum {
+ 	NFSD_CMD_EXPKEY_GET_REQS,
+ 	NFSD_CMD_EXPKEY_SET_REQS,
+ 	NFSD_CMD_CACHE_FLUSH,
++	NFSD_CMD_UNLOCK_IP,
++	NFSD_CMD_UNLOCK_FILESYSTEM,
++	NFSD_CMD_UNLOCK_EXPORT,
++	NFSD_CMD_SERVER_STATS_GET,
+ 
+ 	__NFSD_CMD_MAX,
+ 	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
+diff --git a/support/include/nfsdnl.h b/support/include/nfsdnl.h
+new file mode 100644
+index 00000000..352801e5
+--- /dev/null
++++ b/support/include/nfsdnl.h
+@@ -0,0 +1,34 @@
++/*
++ * Helper for sending nfsd generic netlink commands.
++ *
++ * Used by both nfsdctl and exportfs.
++ */
++
++#ifndef NFS_UTILS_NFSDNL_H
++#define NFS_UTILS_NFSDNL_H
++
++#ifdef HAVE_NFSD_NETLINK
++
++/**
++ * nfsd_nl_cmd_str - send an nfsd netlink command carrying a string attribute
++ * @cmd:   NFSD_CMD_* command number
++ * @attr:  NFSD_A_* attribute number
++ * @value: NUL-terminated string value for the attribute
++ *
++ * Opens a genetlink connection, resolves the "nfsd" family, sends a
++ * single "do" command with one string attribute, waits for the ACK,
++ * and cleans up.
++ *
++ * Returns 0 on success or a negative errno on failure.
++ */
++int nfsd_nl_cmd_str(int cmd, int attr, const char *value);
++
++#else
++
++static inline int nfsd_nl_cmd_str(int cmd, int attr, const char *value)
++{
++	return -ENOSYS;
++}
++
++#endif /* HAVE_NFSD_NETLINK */
++#endif /* NFS_UTILS_NFSDNL_H */
+diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
+index 5bfd71a9..64ad5d07 100644
+--- a/support/nfs/Makefile.am
++++ b/support/nfs/Makefile.am
+@@ -11,6 +11,13 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
+ libnfs_la_LIBADD = libnfsconf.la -luuid
+ libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
+ 
++if CONFIG_NFSDCTL
++libnfs_la_SOURCES += nfsdnl.c
++libnfs_la_CPPFLAGS += $(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS) \
++		      -I$(top_srcdir)/utils/nfsdctl
++libnfs_la_LIBADD += $(LIBNL3_LIBS) $(LIBNLGENL3_LIBS)
++endif
++
+ libnfsconf_la_SOURCES = conffile.c xlog.c
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+diff --git a/support/nfs/fh_key_file.c b/support/nfs/fh_key_file.c
+index 5f5eafc1..81ea1500 100644
+--- a/support/nfs/fh_key_file.c
++++ b/support/nfs/fh_key_file.c
+@@ -26,6 +26,7 @@
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <errno.h>
++#include <string.h>
+ #include <uuid/uuid.h>
+ 
+ #include "nfslib.h"
+diff --git a/support/nfs/getport.c b/support/nfs/getport.c
+index 813f7bf9..608e185b 100644
+--- a/support/nfs/getport.c
++++ b/support/nfs/getport.c
+@@ -452,11 +452,12 @@ char *nfs_sockaddr2universal(const struct sockaddr *sap)
+ 	uint16_t port;
+ 	size_t count;
+ 	char *result;
+-	int len;
++	int len = sizeof(struct sockaddr);
+ 
+ 	switch (sap->sa_family) {
+ 	case AF_LOCAL:
+-		return strndup(sun->sun_path, sizeof(sun->sun_path));
++		size_t path_len = len - offsetof(struct sockaddr_un, sun_path);
++		return strndup(sun->sun_path, path_len);
+ 	case AF_INET:
+ 		if (inet_ntop(AF_INET, (const void *)&sin->sin_addr.s_addr,
+ 					buf, (socklen_t)sizeof(buf)) == NULL)
+diff --git a/support/nfs/nfsdnl.c b/support/nfs/nfsdnl.c
+new file mode 100644
+index 00000000..ded035b9
+--- /dev/null
++++ b/support/nfs/nfsdnl.c
+@@ -0,0 +1,125 @@
++/*
++ * nfsdnl.c -- send nfsd generic netlink commands
++ *
++ * Helper shared by nfsdctl and exportfs.
++ */
++
++#ifdef HAVE_CONFIG_H
++#include <config.h>
++#endif
++
++#include <errno.h>
++#include <string.h>
++
++#include <netlink/genl/genl.h>
++#include <netlink/genl/ctrl.h>
++#include <netlink/msg.h>
++#include <netlink/attr.h>
++
++#include "nfslib.h"
++#include "xlog.h"
++#include "nfsdnl.h"
++
++#ifdef USE_SYSTEM_NFSD_NETLINK_H
++#include <linux/nfsd_netlink.h>
++#else
++#include "nfsd_netlink.h"
++#endif
++
++#define NFSDNL_BUFSIZE	(4096)
++
++static int error_handler(struct sockaddr_nl *UNUSED(nla), struct nlmsgerr *err,
++			 void *arg)
++{
++	int *ret = arg;
++	*ret = err->error;
++	return NL_STOP;
++}
++
++static int finish_handler(struct nl_msg *UNUSED(msg), void *arg)
++{
++	int *ret = arg;
++	*ret = 0;
++	return NL_SKIP;
++}
++
++static int ack_handler(struct nl_msg *UNUSED(msg),
++		       void *arg)
++{
++	int *ret = arg;
++	*ret = 0;
++	return NL_STOP;
++}
++
++/**
++ * nfsd_nl_cmd_str - send an nfsd netlink command carrying a string attribute
++ * @cmd:   NFSD_CMD_* command number
++ * @attr:  NFSD_A_* attribute number
++ * @value: NUL-terminated string value for the attribute
++ *
++ * Returns 0 on success or a negative errno on failure.
++ */
++int nfsd_nl_cmd_str(int cmd, int attr, const char *value)
++{
++	struct genlmsghdr *ghdr;
++	struct nl_sock *sock;
++	struct nl_msg *msg;
++	struct nl_cb *cb;
++	int family;
++	int ret;
++
++	sock = nl_socket_alloc();
++	if (!sock)
++		return -ENOMEM;
++	if (genl_connect(sock)) {
++		ret = -ECONNREFUSED;
++		goto out_sock;
++	}
++	nl_socket_set_buffer_size(sock, NFSDNL_BUFSIZE, NFSDNL_BUFSIZE);
++
++	family = genl_ctrl_resolve(sock, NFSD_FAMILY_NAME);
++	if (family < 0) {
++		ret = family;
++		goto out_sock;
++	}
++
++	msg = nlmsg_alloc();
++	if (!msg) {
++		ret = -ENOMEM;
++		goto out_sock;
++	}
++	if (!genlmsg_put(msg, 0, 0, family, 0, 0, 0, 0)) {
++		ret = -ENOMEM;
++		goto out_msg;
++	}
++
++	ghdr = nlmsg_data(nlmsg_hdr(msg));
++	ghdr->cmd = (__u8)cmd;
++	nla_put_string(msg, attr, value);
++
++	cb = nl_cb_alloc(NL_CB_CUSTOM);
++	if (!cb) {
++		ret = -ENOMEM;
++		goto out_msg;
++	}
++
++	ret = nl_send_auto(sock, msg);
++	if (ret < 0)
++		goto out_cb;
++
++	ret = 1;
++	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
++	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret);
++	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
++
++	while (ret > 0)
++		nl_recvmsgs(sock, cb);
++
++out_cb:
++	nl_cb_put(cb);
++out_msg:
++	nlmsg_free(msg);
++out_sock:
++	nl_socket_free(sock);
++	return ret;
++}
+diff --git a/support/nfsidmap/libnfsidmap.c b/support/nfsidmap/libnfsidmap.c
+index e1475879..5377a0ee 100644
+--- a/support/nfsidmap/libnfsidmap.c
++++ b/support/nfsidmap/libnfsidmap.c
+@@ -409,15 +409,14 @@ int nfs4_init_name_mapping(char *conffile)
+ 	if (idmap_verbosity >= 1) {
+ 		struct conf_list_node *r;
+ 		char *buf = NULL;
+-		int siz=0;
++		size_t siz = 1;
+ 
+ 		if (local_realms) {
+ 			TAILQ_FOREACH(r, &local_realms->fields, link) {
+ 				siz += (strlen(r->field)+4);
+ 			}
+-			buf = malloc(siz);
++			buf = calloc(1, siz);
+ 			if (buf) {
+-				*buf = 0;
+ 				TAILQ_FOREACH(r, &local_realms->fields, link) {
+ 					sprintf(buf+strlen(buf), "'%s' ", r->field);
+ 				}
+diff --git a/support/reexport/backend_sqlite.c b/support/reexport/backend_sqlite.c
+index 0eb5ea37..a1e981e4 100644
+--- a/support/reexport/backend_sqlite.c
++++ b/support/reexport/backend_sqlite.c
+@@ -9,6 +9,8 @@
+ #include <string.h>
+ #include <unistd.h>
+ 
++#include <sys/syscall.h>
++
+ #ifdef HAVE_GETRANDOM
+ # include <sys/random.h>
+ # if !defined(SYS_getrandom) && defined(__NR_getrandom)
+diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py
+index 69d24a11..af04aac5 100755
+--- a/tools/nfs-iostat/nfs-iostat.py
++++ b/tools/nfs-iostat/nfs-iostat.py
+@@ -327,7 +327,7 @@ class DeviceData:
+             print()
+             print('%d congestion waits' % congestionwaits)
+ 
+-    def __print_rpc_op_stats(self, op, sample_time):
++    def __print_rpc_op_stats(self, op, sample_time, use_mb=False):
+         """Print generic stats for one RPC op
+         """
+         if op not in self.__rpc_data:
+@@ -343,9 +343,19 @@ class DeviceData:
+         if len(rpc_stats) >= 9:
+             errs = float(rpc_stats[8])
+ 
++        # scale to MB if requested
++        if use_mb:
++            throughput = kilobytes / 1024.0
++            throughput_label = 'MB/s'
++            per_op_label = 'MB/op'
++        else:
++            throughput = kilobytes
++            throughput_label = 'kB/s'
++            per_op_label = 'kB/op'
++
+         # prevent floating point exceptions
+         if ops != 0:
+-            kb_per_op = kilobytes / ops
++            unit_per_op = throughput / ops
+             retrans_percent = (retrans * 100) / ops
+             rtt_per_op = rtt / ops
+             exe_per_op = exe / ops
+@@ -353,7 +363,7 @@ class DeviceData:
+             if len(rpc_stats) >= 9:
+                 errs_percent = (errs * 100) / ops
+         else:
+-            kb_per_op = 0.0
++            unit_per_op = 0.0
+             retrans_percent = 0.0
+             rtt_per_op = 0.0
+             exe_per_op = 0.0
+@@ -364,8 +374,8 @@ class DeviceData:
+         op += ':'
+         print(format(op.lower(), '<16s'), end='')
+         print(format('ops/s', '>8s'), end='')
+-        print(format('kB/s', '>16s'), end='')
+-        print(format('kB/op', '>16s'), end='')
++        print(format(throughput_label, '>16s'), end='')
++        print(format(per_op_label, '>16s'), end='')
+         print(format('retrans', '>16s'), end='')
+         print(format('avg RTT (ms)', '>16s'), end='')
+         print(format('avg exe (ms)', '>16s'), end='')
+@@ -375,8 +385,8 @@ class DeviceData:
+         print()
+ 
+         print(format((ops / sample_time), '>24.3f'), end='')
+-        print(format((kilobytes / sample_time), '>16.3f'), end='')
+-        print(format(kb_per_op, '>16.3f'), end='')
++        print(format((throughput / sample_time), '>16.3f'), end='')
++        print(format(unit_per_op, '>16.3f'), end='')
+         retransmits = '{0:>10.0f} ({1:>3.1f}%)'.format(retrans, retrans_percent).strip()
+         print(format(retransmits, '>16'), end='')
+         print(format(rtt_per_op, '>16.3f'), end='')
+@@ -395,9 +405,11 @@ class DeviceData:
+             sample_time = 1;
+         return (sends / sample_time)
+ 
+-    def display_iostats(self, sample_time, which):
++    def display_iostats(self, sample_time, options):
+         """Display NFS and RPC stats in an iostat-like way
+         """
++        which = options.which
++        use_mb = options.megabytes
+         sends = float(self.__rpc_data['rpcsends'])
+         if sample_time == 0:
+             sample_time = float(self.__nfs_data['age'])
+@@ -423,21 +435,21 @@ class DeviceData:
+         print()
+ 
+         if which == 0:
+-            self.__print_rpc_op_stats('READ', sample_time)
+-            self.__print_rpc_op_stats('WRITE', sample_time)
++            self.__print_rpc_op_stats('READ', sample_time, use_mb)
++            self.__print_rpc_op_stats('WRITE', sample_time, use_mb)
+         elif which == 1:
+-            self.__print_rpc_op_stats('GETATTR', sample_time)
+-            self.__print_rpc_op_stats('ACCESS', sample_time)
++            self.__print_rpc_op_stats('GETATTR', sample_time, use_mb)
++            self.__print_rpc_op_stats('ACCESS', sample_time, use_mb)
+             self.__print_attr_cache_stats(sample_time)
+         elif which == 2:
+-            self.__print_rpc_op_stats('LOOKUP', sample_time)
+-            self.__print_rpc_op_stats('READDIR', sample_time)
++            self.__print_rpc_op_stats('LOOKUP', sample_time, use_mb)
++            self.__print_rpc_op_stats('READDIR', sample_time, use_mb)
+             if 'READDIRPLUS' in self.__rpc_data:
+-                self.__print_rpc_op_stats('READDIRPLUS', sample_time)
++                self.__print_rpc_op_stats('READDIRPLUS', sample_time, use_mb)
+             self.__print_dir_cache_stats(sample_time)
+         elif which == 3:
+-            self.__print_rpc_op_stats('READ', sample_time)
+-            self.__print_rpc_op_stats('WRITE', sample_time)
++            self.__print_rpc_op_stats('READ', sample_time, use_mb)
++            self.__print_rpc_op_stats('WRITE', sample_time, use_mb)
+             self.__print_page_stats(sample_time)
+ 
+         sys.stdout.flush()
+@@ -500,7 +512,7 @@ def print_iostat_summary(old, new, devices, time, options):
+ 
+     count = 1
+     for device in devices:
+-        display_stats[device].display_iostats(time, options.which)
++        display_stats[device].display_iostats(time, options)
+ 
+         count += 1
+         if (count > options.list):
+@@ -585,6 +597,11 @@ client are listed.
+                             type="int",
+                             dest="list",
+                             help="only print stats for first LIST mount points")
++    displaygroup.add_option('-m', '--megabytes',
++                            action="store_true",
++                            dest="megabytes",
++                            default=False,
++                            help="display throughput in megabytes per second (MB/s) instead of kilobytes per second (kB/s)")
+     parser.add_option_group(displaygroup)
+ 
+     (options, args) = parser.parse_args(sys.argv)
+diff --git a/tools/nfs-iostat/nfsiostat.man b/tools/nfs-iostat/nfsiostat.man
+index 104c7ab4..4f24318d 100644
+--- a/tools/nfs-iostat/nfsiostat.man
++++ b/tools/nfs-iostat/nfsiostat.man
+@@ -56,16 +56,16 @@ This is the length of the backlog queue.
+ .RE
+ .RE
+ .RS 8
+-- \fBkB/s\fR
++- \fBkB/s (MB/s)\fR
+ .RS
+-This is the number of kB written/read per second.
++This is the number of kB (or MB) written/read per second.
+ .RE
+ .RE
+ .RE
+ .RS 8
+-- \fBkB/op\fR
++- \fBkB/op (MB/op)\fR
+ .RS
+-This is the number of kB written/read per each operation.
++This is the number of kB (or MB) written/read per each operation.
+ .RE
+ .RE
+ .RE
+@@ -122,6 +122,9 @@ shows help message and exit
+ .B \-l LIST or " \-\-list=LIST 
+ only print stats for first LIST mount points
+ .TP
++.B \-m or " \-\-megabytes
++display throughput in megabytes per second
++.TP
+ .B \-p " or " \-\-page
+ displays statistics related to the page cache
+ .TP
+diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
+index 93f0bcd7..768d2db7 100644
+--- a/utils/exportfs/exportfs.c
++++ b/utils/exportfs/exportfs.c
+@@ -39,6 +39,15 @@
+ #include "xlog.h"
+ #include "conffile.h"
+ #include "reexport.h"
++#include "nfsdnl.h"
++
++#ifdef HAVE_NFSD_NETLINK
++#ifdef USE_SYSTEM_NFSD_NETLINK_H
++#include <linux/nfsd_netlink.h>
++#else
++#include "nfsd_netlink.h"
++#endif
++#endif
+ 
+ #include <netlink/genl/genl.h>
+ #include <netlink/genl/ctrl.h>
+@@ -63,6 +72,7 @@ static void release_lockfile(void);
+ 
+ static const char *lockfile = EXP_LOCKFILE;
+ static int _lockfd = -1;
++static int f_unexport_all;
+ 
+ /*
+  * If we aren't careful, changes made by exportfs can be lost
+@@ -246,7 +256,8 @@ main(int argc, char **argv)
+ 	 * don't care about what should be exported, as that
+ 	 * may require DNS lookups..
+ 	 */
+-	if (! ( !f_export && f_all)) {
++	f_unexport_all = !f_export && f_all;
++	if (!f_unexport_all) {
+ 		/* note: xtab_*_read does not update entries if they already exist,
+ 		 * so this will not lose new options
+ 		 */
+@@ -380,6 +391,26 @@ exportfs(char *arg, char *options, int verbose)
+ 		xlog(L_ERROR, "Invalid export syntax: %s", arg);
+ }
+ 
++/*
++ * Check whether any active export remains for the given path across
++ * all client types.  Returns true if at least one export still has
++ * m_xtabent set.
++ */
++static int
++path_still_exported(const char *path, size_t nlen)
++{
++	nfs_export *exp;
++	int i;
++
++	for (i = 0; i < MCL_MAXTYPES; i++)
++		for (exp = exportlist[i].p_head; exp; exp = exp->m_next)
++			if (exp->m_xtabent &&
++			    strlen(exp->m_export.e_path) == nlen &&
++			    strncmp(path, exp->m_export.e_path, nlen) == 0)
++				return 1;
++	return 0;
++}
++
+ static void
+ unexportfs_parsed(char *hname, char *path, int verbose)
+ {
+@@ -434,9 +465,39 @@ unexportfs_parsed(char *hname, char *path, int verbose)
+ 		exp->m_mayexport = 0;
+ 		success = 1;
+ 	}
+-	if (!success)
++	if (!success) {
+ 		xlog(L_ERROR, "Could not find '%s:%s' to unexport.", hname, path);
++		goto out;
++	}
+ 
++	/*
++	 * If no exports remain for this path, ask the kernel to
++	 * revoke any NFSv4 state and close cached file handles
++	 * associated with exports of this path.  This enables the
++	 * underlying filesystem to be unmounted.
++	 *
++	 * Skip this during "exportfs -ua" -- that is a shutdown
++	 * operation.  Clients should wait for nfsd to restart and
++	 * reclaim state through the grace period rather than
++	 * receiving NFS4ERR_ADMIN_REVOKED.
++	 */
++#ifdef HAVE_NFSD_NETLINK
++	if (!f_unexport_all && !path_still_exported(path, nlen)) {
++		char pathbuf[NFS_MAXPATHLEN + 1];
++		int ret;
++
++		memcpy(pathbuf, path, nlen);
++		pathbuf[nlen] = '\0';
++		ret = nfsd_nl_cmd_str(NFSD_CMD_UNLOCK_EXPORT,
++				      NFSD_A_UNLOCK_EXPORT_PATH,
++				      pathbuf);
++		if (ret && ret != -ENOSYS)
++			xlog(L_WARNING,
++			     "Failed to release state for %s: %s",
++			     pathbuf, strerror(-ret));
++	}
++#endif
++out:
+ 	nfs_freeaddrinfo(ai);
+ }
+ 
+diff --git a/utils/exportfs/exportfs.man b/utils/exportfs/exportfs.man
+index 3737ee81..b5e0c63a 100644
+--- a/utils/exportfs/exportfs.man
++++ b/utils/exportfs/exportfs.man
+@@ -256,6 +256,23 @@ pair. This deletes the specified entry from
+ .I /var/lib/nfs/etab
+ and removes the corresponding kernel entry (if any).
+ .PP
++When the last client for a given export path is unexported,
++.B exportfs
++signals the kernel to revoke NFSv4 state (opens, locks, and
++delegations) and release cached state for that path.
++Without this revocation, retained state would prevent the
++underlying filesystem from being unmounted.
++Affected clients receive
++.B NFS4ERR_ADMIN_REVOKED
++errors for operations that use revoked state.
++.PP
++.B "exportfs \-ua"
++does not revoke NFSv4 state, however.
++If
++.B nfsd
++is then restarted, clients may reclaim state during the
++grace period.
++.PP
+ .SS Dumping the Export Table
+ Invoking
+ .B exportfs
+diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
+index 016dd2eb..c7126748 100644
+--- a/utils/nfsdctl/nfsdctl.c
++++ b/utils/nfsdctl/nfsdctl.c
+@@ -26,6 +26,7 @@
+ #include <netlink/msg.h>
+ #include <netlink/attr.h>
+ #include <linux/netlink.h>
++#include "compat.h"
+ 
+ #include <readline/readline.h>
+ #include <readline/history.h>
+diff --git a/utils/nfsstat/Makefile.am b/utils/nfsstat/Makefile.am
+index d1555a7d..8b121c48 100644
+--- a/utils/nfsstat/Makefile.am
++++ b/utils/nfsstat/Makefile.am
+@@ -5,8 +5,10 @@ EXTRA_DIST	= $(man8_MANS)
+ 
+ sbin_PROGRAMS	= nfsstat
+ nfsstat_SOURCES = nfsstat.c
++nfsstat_CFLAGS = $(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS)
+ nfsstat_LDADD = ../../support/export/libexport.a \
+ 	      	../../support/nfs/libnfs.la \
+-		../../support/misc/libmisc.a
++		../../support/misc/libmisc.a \
++		$(LIBNL3_LIBS) $(LIBNLGENL3_LIBS)
+ 
+ MAINTAINERCLEANFILES = Makefile.in
+diff --git a/utils/nfsstat/nfsstat.c b/utils/nfsstat/nfsstat.c
+index ca845325..f09e1d6a 100644
+--- a/utils/nfsstat/nfsstat.c
++++ b/utils/nfsstat/nfsstat.c
+@@ -23,6 +23,17 @@
+ #include <signal.h>
+ #include <time.h>
+ 
++#include <netlink/genl/genl.h>
++#include <netlink/genl/ctrl.h>
++#include <netlink/msg.h>
++#include <netlink/attr.h>
++
++#ifdef USE_SYSTEM_NFSD_NETLINK_H
++#include <linux/nfsd_netlink.h>
++#else
++#include "nfsd_netlink.h"
++#endif
++
+ #define MAXNRVALS	32
+ 
+ enum {
+@@ -271,6 +282,7 @@ static statinfo		*get_stat_info(const char *, struct statinfo *);
+ 
+ static int		mounts(const char *);
+ 
++static int		get_stats_netlink(struct statinfo *);
+ static void		get_stats(const char *, struct statinfo *, int *, int,
+ 					int);
+ static int		has_stats(const unsigned int *, int);
+@@ -1051,6 +1063,272 @@ mounts(const char *name)
+ 	return 1;
+ }
+ 
++/*
++ * Netlink helpers for fetching server stats via Generic Netlink.
++ */
++static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
++			    void *arg)
++{
++	int *ret = arg;
++
++	*ret = err->error;
++	return NL_SKIP;
++}
++
++static int nl_finish_handler(struct nl_msg *msg, void *arg)
++{
++	int *ret = arg;
++
++	*ret = 0;
++	return NL_SKIP;
++}
++
++static int nl_ack_handler(struct nl_msg *msg, void *arg)
++{
++	int *ret = arg;
++
++	*ret = 0;
++	return NL_STOP;
++}
++
++static void parse_one_proc_entry(struct nlattr *nest, unsigned int *info,
++				 unsigned int max_ops)
++{
++	struct nlattr *tb[NFSD_A_SERVER_PROC_ENTRY_MAX + 1];
++	unsigned int op, count;
++
++	nla_parse_nested(tb, NFSD_A_SERVER_PROC_ENTRY_MAX, nest, NULL);
++	if (!tb[NFSD_A_SERVER_PROC_ENTRY_OP] ||
++	    !tb[NFSD_A_SERVER_PROC_ENTRY_COUNT])
++		return;
++
++	op = nla_get_u32(tb[NFSD_A_SERVER_PROC_ENTRY_OP]);
++	count = (unsigned int)nla_get_u64(tb[NFSD_A_SERVER_PROC_ENTRY_COUNT]);
++	if (op < max_ops) {
++		info[0] = max_ops;
++		info[op + 1] = count;
++	}
++}
++
++static int stats_nl_handler(struct nl_msg *msg, void *arg)
++{
++	struct statinfo *info = arg;
++	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
++	struct nlattr *attr;
++	statinfo *si;
++	int rem;
++
++	nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0),
++			  genlmsg_attrlen(gnlh, 0), rem) {
++		int type = nla_type(attr);
++
++		switch (type) {
++		/* Reply cache */
++		case NFSD_A_SERVER_STATS_RC_HITS:
++			si = get_stat_info("rc", info);
++			if (si)
++				si->valptr[0] = nla_get_u64(attr);
++			break;
++		case NFSD_A_SERVER_STATS_RC_MISSES:
++			si = get_stat_info("rc", info);
++			if (si)
++				si->valptr[1] = nla_get_u64(attr);
++			break;
++		case NFSD_A_SERVER_STATS_RC_NOCACHE:
++			si = get_stat_info("rc", info);
++			if (si)
++				si->valptr[2] = nla_get_u64(attr);
++			break;
++
++		/* Filehandle */
++		case NFSD_A_SERVER_STATS_FH_STALE:
++			si = get_stat_info("fh", info);
++			if (si)
++				si->valptr[0] = nla_get_u64(attr);
++			break;
++
++		/* IO */
++		case NFSD_A_SERVER_STATS_IO_READ:
++			si = get_stat_info("io", info);
++			if (si)
++				si->valptr[0] = nla_get_u64(attr);
++			break;
++		case NFSD_A_SERVER_STATS_IO_WRITE:
++			si = get_stat_info("io", info);
++			if (si)
++				si->valptr[1] = nla_get_u64(attr);
++			break;
++
++		/* Network */
++		case NFSD_A_SERVER_STATS_NETCNT:
++			si = get_stat_info("net", info);
++			if (si)
++				si->valptr[0] = nla_get_u32(attr);
++			break;
++		case NFSD_A_SERVER_STATS_NETUDPCNT:
++			si = get_stat_info("net", info);
++			if (si)
++				si->valptr[1] = nla_get_u32(attr);
++			break;
++		case NFSD_A_SERVER_STATS_NETTCPCNT:
++			si = get_stat_info("net", info);
++			if (si)
++				si->valptr[2] = nla_get_u32(attr);
++			break;
++		case NFSD_A_SERVER_STATS_NETTCPCONN:
++			si = get_stat_info("net", info);
++			if (si)
++				si->valptr[3] = nla_get_u32(attr);
++			break;
++
++		/* RPC */
++		case NFSD_A_SERVER_STATS_RPCCNT:
++			si = get_stat_info("rpc", info);
++			if (si)
++				si->valptr[0] = nla_get_u32(attr);
++			break;
++		case NFSD_A_SERVER_STATS_RPCBADFMT:
++			si = get_stat_info("rpc", info);
++			if (si)
++				si->valptr[2] = nla_get_u32(attr);
++			break;
++		case NFSD_A_SERVER_STATS_RPCBADAUTH:
++			si = get_stat_info("rpc", info);
++			if (si)
++				si->valptr[3] = nla_get_u32(attr);
++			break;
++		case NFSD_A_SERVER_STATS_RPCBADCLNT:
++			si = get_stat_info("rpc", info);
++			if (si)
++				si->valptr[4] = nla_get_u32(attr);
++			break;
++
++		/* Per-version procedure counts (multi-attr) */
++		case NFSD_A_SERVER_STATS_PROC2_OPS:
++			si = get_stat_info("proc2", info);
++			if (si)
++				parse_one_proc_entry(attr, si->valptr,
++						     SRVPROC2_SZ);
++			break;
++		case NFSD_A_SERVER_STATS_PROC3_OPS:
++			si = get_stat_info("proc3", info);
++			if (si)
++				parse_one_proc_entry(attr, si->valptr,
++						     SRVPROC3_SZ);
++			break;
++		case NFSD_A_SERVER_STATS_PROC4_OPS:
++			si = get_stat_info("proc4", info);
++			if (si)
++				parse_one_proc_entry(attr, si->valptr,
++						     SRVPROC4_SZ);
++			break;
++		case NFSD_A_SERVER_STATS_PROC4OPS_OPS:
++			si = get_stat_info("proc4ops", info);
++			if (si)
++				parse_one_proc_entry(attr, si->valptr,
++						     SRVPROC4OPS_SZ);
++			break;
++		}
++	}
++
++	return NL_OK;
++}
++
++/*
++ * Fetch server stats via Generic Netlink.
++ * Returns 0 on success, -1 on failure.
++ */
++static int
++get_stats_netlink(struct statinfo *info)
++{
++	struct nl_sock *sock;
++	struct nl_msg *msg;
++	struct nl_cb *cb;
++	int family, ret;
++	statinfo *si;
++
++	sock = nl_socket_alloc();
++	if (!sock)
++		return -1;
++
++	if (genl_connect(sock)) {
++		nl_socket_free(sock);
++		return -1;
++	}
++
++	family = genl_ctrl_resolve(sock, NFSD_FAMILY_NAME);
++	if (family < 0) {
++		nl_socket_free(sock);
++		return -1;
++	}
++
++	msg = nlmsg_alloc();
++	if (!msg) {
++		nl_socket_free(sock);
++		return -1;
++	}
++
++	if (!genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0,
++			 NLM_F_DUMP, NFSD_CMD_SERVER_STATS_GET, 0)) {
++		nlmsg_free(msg);
++		nl_socket_free(sock);
++		return -1;
++	}
++
++	cb = nl_cb_alloc(NL_CB_CUSTOM);
++	if (!cb) {
++		nlmsg_free(msg);
++		nl_socket_free(sock);
++		return -1;
++	}
++
++	ret = nl_send_auto(sock, msg);
++	if (ret < 0) {
++		nl_cb_put(cb);
++		nlmsg_free(msg);
++		nl_socket_free(sock);
++		return -1;
++	}
++
++	ret = 1;
++	nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &ret);
++	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &ret);
++	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &ret);
++	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, stats_nl_handler, info);
++
++	while (ret > 0)
++		nl_recvmsgs(sock, cb);
++
++	nl_cb_put(cb);
++	nlmsg_free(msg);
++	nl_socket_free(sock);
++
++	if (ret < 0)
++		return -1;
++
++	/*
++	 * Compute derived fields. The proc file emits "rpc rpccnt
++	 * badcalls badfmt badauth badclnt" where badcalls is the sum
++	 * of badfmt+badauth+badclnt. The netlink interface sends the
++	 * components individually, so recompute the sum here.
++	 */
++	si = get_stat_info("rpc", info);
++	if (si)
++		si->valptr[1] = si->valptr[2] + si->valptr[3] + si->valptr[4];
++
++	/* Compute totals for each stat category */
++	for (si = info; si->tag; si++) {
++		unsigned int total = 0;
++		int i;
++
++		for (i = 0; i < si->nrvals - 1; i++)
++			total += si->valptr[i];
++		si->valptr[si->nrvals - 1] = total;
++	}
++
++	return 0;
++}
++
+ static void
+ get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
+ 		int is_srv)
+@@ -1060,6 +1338,10 @@ get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
+ 	int err = 1;
+ 	char *label = is_srv ? "Server" : "Client";
+ 
++	/* Try netlink first for server stats */
++	if (is_srv && get_stats_netlink(info) == 0)
++		return;
++
+ 	/* try to guess what type of stat file we're dealing with */
+ 	if ((fp = fopen(file, "r")) == NULL)
+ 		goto out;

diff --git a/nfs-utils.spec b/nfs-utils.spec
index d31d606..316e681 100644
--- a/nfs-utils.spec
+++ b/nfs-utils.spec
@@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser
 Name: nfs-utils
 URL: http://linux-nfs.org/
 Version: 2.9.1
-Release: 3.rc3%{?dist}
+Release: 3.rc4%{?dist}
 Epoch: 1
 
 # group all 32bit related archs
@@ -15,7 +15,7 @@ Source3: 24-nfs-server.conf
 Source4: 10-nfsv4.conf
 Source5: 10-nfsv3.conf
 
-Patch001: nfs-utils-2.9.2-rc3.patch
+Patch001: nfs-utils-2.9.2-rc4.patch
 
 Patch100: nfs-utils-1.2.1-statdpath-man.patch
 Patch102: nfs-utils-1.2.5-idmap-errmsg.patch
@@ -476,6 +476,9 @@ rm -f %{_sysconfdir}/nfsmount.conf.d/10-nfsv4.conf
 %{_mandir}/*/rpcctl.8.gz
 
 %changelog
+* Sat May 30 2026 Steve Dickson <steved@redhat.com> 2.9.1-2-rc4
+- Updated to the latest RC release: nfs-utils-2-9-2-rc4
+
 * Wed May 20 2026 Steve Dickson <steved@redhat.com> 2.9.1-3-rc3
 - Add %%ghost to clean up 10-nfsv4.conf and 10-nfsv3.conf declarations
 

                 reply	other threads:[~2026-05-31 12:23 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=178023020042.1.6044723889829551561.rpms-nfs-utils-1a9c20bc3cc9@fedoraproject.org \
    --to=steved@redhat.com \
    --cc=git-commits@fedoraproject.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