* [PATCH v1 1/3] nfsdctl: report NFSv4 grace state
2026-08-25 6:52 [PATCH v1 0/3] nfsdctl: list NFSv4 clients Prabhakar Pujeri
@ 2026-08-25 6:52 ` Prabhakar Pujeri
2026-08-25 6:52 ` [PATCH v1 2/3] nfsdctl: add a command to list NFSv4 clients Prabhakar Pujeri
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:52 UTC (permalink / raw)
To: steved; +Cc: cel, jlayton, linux-nfs, Prabhakar Pujeri
The threads command reports the server's thread configuration, but an
operator also needs to know whether the NFSv4 server is accepting normal
state operations or only recovery requests.
Display the kernel's new in-grace value when the attribute is present.
Checking its presence prevents a new nfsdctl from reporting an older
kernel's lack of the attribute as "no".
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
configure.ac | 2 +-
support/include/nfsd_netlink.h | 1 +
utils/nfsdctl/nfsdctl.8 | 3 ++-
utils/nfsdctl/nfsdctl.adoc | 3 ++-
utils/nfsdctl/nfsdctl.c | 8 ++++++++
5 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/configure.ac b/configure.ac
index 2ce1e12..96aa82c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -259,7 +259,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_SERVER_STATS_GET;]])],
+ [[int foo = NFSD_A_SERVER_IN_GRACE;]])],
[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/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
index 87da1d0..e1fc2db 100644
--- a/support/include/nfsd_netlink.h
+++ b/support/include/nfsd_netlink.h
@@ -84,6 +84,7 @@ enum {
NFSD_A_SERVER_SCOPE,
NFSD_A_SERVER_MIN_THREADS,
NFSD_A_SERVER_FH_KEY,
+ NFSD_A_SERVER_IN_GRACE,
__NFSD_A_SERVER_MAX,
NFSD_A_SERVER_MAX = (__NFSD_A_SERVER_MAX - 1)
diff --git a/utils/nfsdctl/nfsdctl.8 b/utils/nfsdctl/nfsdctl.8
index 1f526e7..58d5cd9 100644
--- a/utils/nfsdctl/nfsdctl.8
+++ b/utils/nfsdctl/nfsdctl.8
@@ -144,7 +144,8 @@ takes no arguments.
Get/set the number of running nfsd threads in each pool. Pass a list of
integers to change the currently active number of threads. Passing it a
value of 0 will shut down the NFS server. Run this without arguments to
-get the current number of running threads in each pool.
+get the current number of running threads in each pool and whether the
+NFSv4 server is in its recovery grace period.
.RE
.sp
.if n .RS 4
diff --git a/utils/nfsdctl/nfsdctl.adoc b/utils/nfsdctl/nfsdctl.adoc
index e85348e..7e03933 100644
--- a/utils/nfsdctl/nfsdctl.adoc
+++ b/utils/nfsdctl/nfsdctl.adoc
@@ -82,7 +82,8 @@ Each subcommand can also accept its own set of options and arguments. The
Get/set the number of running nfsd threads in each pool. Pass a list of
integers to change the currently active number of threads. Passing it a
value of 0 will shut down the NFS server. Run this without arguments to
- get the current number of running threads in each pool.
+ get the current number of running threads in each pool and whether the
+ NFSv4 server is in its recovery grace period.
[source,bash]
----
diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
index c712674..2b1faef 100644
--- a/utils/nfsdctl/nfsdctl.c
+++ b/utils/nfsdctl/nfsdctl.c
@@ -311,6 +311,8 @@ static void parse_threads_get(struct genlmsghdr *gnlh)
struct nlattr *attr;
int rem, pools = 0, i = 0;
uint32_t *pool_threads = NULL;
+ bool in_grace = false;
+ bool have_in_grace = false;
nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), rem)
@@ -337,6 +339,10 @@ static void parse_threads_get(struct genlmsghdr *gnlh)
case NFSD_A_SERVER_MIN_THREADS:
printf("min-threads: %u\n", nla_get_u32(attr));
break;
+ case NFSD_A_SERVER_IN_GRACE:
+ in_grace = nla_get_u8(attr);
+ have_in_grace = true;
+ break;
default:
break;
}
@@ -346,6 +352,8 @@ static void parse_threads_get(struct genlmsghdr *gnlh)
for (i = 0; i < pools; ++i)
printf(" %d", pool_threads[i]);
putchar('\n');
+ if (have_in_grace)
+ printf("in-grace: %s\n", in_grace ? "yes" : "no");
}
static void parse_pool_mode_get(struct genlmsghdr *gnlh)
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v1 2/3] nfsdctl: add a command to list NFSv4 clients
2026-08-25 6:52 [PATCH v1 0/3] nfsdctl: list NFSv4 clients Prabhakar Pujeri
2026-08-25 6:52 ` [PATCH v1 1/3] nfsdctl: report NFSv4 grace state Prabhakar Pujeri
@ 2026-08-25 6:52 ` Prabhakar Pujeri
2026-08-25 6:52 ` [PATCH v1 3/3] nfsdctl: display per-client NFSv4 state usage Prabhakar Pujeri
2026-08-28 14:37 ` [PATCH v1 0/3] nfsdctl: list NFSv4 clients Jeff Layton
3 siblings, 0 replies; 6+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:52 UTC (permalink / raw)
To: steved; +Cc: cel, jlayton, linux-nfs, Prabhakar Pujeri
Monitoring NFSv4 clients currently requires walking the nfsd filesystem
and parsing a separate info file for every client. Add a clients command
that consumes the new NFSD_CMD_CLIENT_GET Netlink dump and presents one
row per client.
Report the server-generated client ID, peer address, minor version,
client state, lease time remaining, reclaim status, and callback state.
Format both IPv4 and IPv6 socket addresses, including their ports and
IPv6 scope IDs. Keep unknown enum values visible as "unknown" so newer
kernels remain usable with this client.
Update the bundled UAPI header and require NFSD_CMD_CLIENT_GET before
selecting a system copy of that header.
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
configure.ac | 2 +-
support/include/nfsd_netlink.h | 35 ++++++
utils/nfsdctl/nfsdctl.8 | 18 +++
utils/nfsdctl/nfsdctl.adoc | 11 ++
utils/nfsdctl/nfsdctl.c | 217 +++++++++++++++++++++++++++++++++
5 files changed, 282 insertions(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 96aa82c..66330d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -259,7 +259,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_A_SERVER_IN_GRACE;]])],
+ [[int foo = NFSD_CMD_CLIENT_GET;]])],
[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/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
index e1fc2db..ae49538 100644
--- a/support/include/nfsd_netlink.h
+++ b/support/include/nfsd_netlink.h
@@ -50,6 +50,26 @@ enum nfsd_xprtsec_mode {
NFSD_XPRTSEC_MODE_MTLS = 4,
};
+/*
+ * State of an NFSv4 client record.
+ */
+enum nfsd_client_state {
+ NFSD_CLIENT_STATE_UNCONFIRMED,
+ NFSD_CLIENT_STATE_ACTIVE,
+ NFSD_CLIENT_STATE_COURTESY,
+ NFSD_CLIENT_STATE_EXPIRABLE,
+};
+
+/*
+ * State of an NFSv4 client's callback channel.
+ */
+enum nfsd_callback_state {
+ NFSD_CALLBACK_STATE_UP,
+ NFSD_CALLBACK_STATE_UNKNOWN,
+ NFSD_CALLBACK_STATE_DOWN,
+ NFSD_CALLBACK_STATE_FAULT,
+};
+
enum {
NFSD_A_CACHE_NOTIFY_CACHE_TYPE = 1,
@@ -261,6 +281,20 @@ enum {
NFSD_A_SERVER_STATS_MAX = (__NFSD_A_SERVER_STATS_MAX - 1)
};
+enum {
+ NFSD_A_CLIENT_CLIENTID = 1,
+ NFSD_A_CLIENT_PAD,
+ NFSD_A_CLIENT_ADDRESS,
+ NFSD_A_CLIENT_MINOR_VERSION,
+ NFSD_A_CLIENT_STATE,
+ NFSD_A_CLIENT_LEASE_REMAINING,
+ NFSD_A_CLIENT_RECLAIM_COMPLETE,
+ NFSD_A_CLIENT_CALLBACK_STATE,
+
+ __NFSD_A_CLIENT_MAX,
+ NFSD_A_CLIENT_MAX = (__NFSD_A_CLIENT_MAX - 1)
+};
+
enum {
NFSD_CMD_RPC_STATUS_GET = 1,
NFSD_CMD_THREADS_SET,
@@ -281,6 +315,7 @@ enum {
NFSD_CMD_UNLOCK_FILESYSTEM,
NFSD_CMD_UNLOCK_EXPORT,
NFSD_CMD_SERVER_STATS_GET,
+ NFSD_CMD_CLIENT_GET,
__NFSD_CMD_MAX,
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
diff --git a/utils/nfsdctl/nfsdctl.8 b/utils/nfsdctl/nfsdctl.8
index 58d5cd9..36af317 100644
--- a/utils/nfsdctl/nfsdctl.8
+++ b/utils/nfsdctl/nfsdctl.8
@@ -81,6 +81,14 @@ This subcommand takes no arguments. Note that if a "threads=" value is not set i
nfs.conf, 16 server threads will be brought online.
.RE
.sp
+\fBclients\fP
+.RS 4
+List NFSv4 clients known to the server. The output reports each client\(cqs
+server-generated client ID, peer address, minor version, lease and callback
+states, lease time remaining, and reclaim status. This subcommand requires
+elevated privileges.
+.RE
+.sp
\fBlistener\fP
.RS 4
Get/set the listening sockets for the server. Run this without arguments to
@@ -260,6 +268,16 @@ nfsdctl listener
.fi
.if n .RE
.sp
+Show the NFSv4 clients known to the server:
+.sp
+.if n .RS 4
+.nf
+.fam C
+nfsdctl clients
+.fam
+.fi
+.if n .RE
+.sp
Show the supported and enabled NFS versions:
.sp
.if n .RS 4
diff --git a/utils/nfsdctl/nfsdctl.adoc b/utils/nfsdctl/nfsdctl.adoc
index 7e03933..3e70b99 100644
--- a/utils/nfsdctl/nfsdctl.adoc
+++ b/utils/nfsdctl/nfsdctl.adoc
@@ -46,6 +46,13 @@ Each subcommand can also accept its own set of options and arguments. The
This subcommand takes no arguments. Note that if a "threads=" value is not set in
nfs.conf, 16 server threads will be brought online.
+*clients*::
+
+ List NFSv4 clients known to the server. The output reports each client's
+ server-generated client ID, peer address, minor version, lease and callback
+ states, lease time remaining, and reclaim status. This subcommand requires
+ elevated privileges.
+
*listener*::
Get/set the listening sockets for the server. Run this without arguments to
@@ -139,6 +146,10 @@ Get a list of current listening sockets:
nfsdctl listener
+Show the NFSv4 clients known to the server:
+
+ nfsdctl clients
+
Show the supported and enabled NFS versions:
nfsdctl version
diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
index 2b1faef..7f12860 100644
--- a/utils/nfsdctl/nfsdctl.c
+++ b/utils/nfsdctl/nfsdctl.c
@@ -8,6 +8,7 @@
#include <linux/version.h>
#include <netlink/genl/genl.h>
#include <errno.h>
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
@@ -84,6 +85,22 @@ struct server_socket {
bool active;
};
+#define MAX_CLIENT_ADDRESS_LEN 80
+
+struct nfsd_client {
+ char address[MAX_CLIENT_ADDRESS_LEN];
+ uint64_t clientid;
+ int64_t lease_remaining;
+ uint32_t minor_version;
+ uint32_t state;
+ uint32_t callback_state;
+ bool reclaim_complete;
+};
+
+struct client_dump_ctx {
+ bool header_printed;
+};
+
#define MAX_NFSD_SOCKETS 256
int nfsd_socket_count;
@@ -241,6 +258,132 @@ static void parse_rpc_status_get(struct genlmsghdr *gnlh)
printf("\n");
}
+static const char *client_state_name(uint32_t state)
+{
+ switch (state) {
+ case NFSD_CLIENT_STATE_UNCONFIRMED:
+ return "unconfirmed";
+ case NFSD_CLIENT_STATE_ACTIVE:
+ return "active";
+ case NFSD_CLIENT_STATE_COURTESY:
+ return "courtesy";
+ case NFSD_CLIENT_STATE_EXPIRABLE:
+ return "expirable";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *callback_state_name(uint32_t state)
+{
+ switch (state) {
+ case NFSD_CALLBACK_STATE_UP:
+ return "up";
+ case NFSD_CALLBACK_STATE_UNKNOWN:
+ return "unknown";
+ case NFSD_CALLBACK_STATE_DOWN:
+ return "down";
+ case NFSD_CALLBACK_STATE_FAULT:
+ return "fault";
+ default:
+ return "unknown";
+ }
+}
+
+static void format_client_address(const struct nlattr *attr, char *buf,
+ size_t buflen)
+{
+ const struct sockaddr *sa = nla_data(attr);
+ char host[INET6_ADDRSTRLEN];
+
+ snprintf(buf, buflen, "%s", "unknown");
+ if (nla_len(attr) < sizeof(sa->sa_family))
+ return;
+
+ switch (sa->sa_family) {
+ case AF_INET: {
+ const struct sockaddr_in *sin = nla_data(attr);
+
+ if (nla_len(attr) < sizeof(*sin) ||
+ !inet_ntop(AF_INET, &sin->sin_addr, host, sizeof(host)))
+ return;
+ snprintf(buf, buflen, "%s:%u", host, ntohs(sin->sin_port));
+ break;
+ }
+ case AF_INET6: {
+ const struct sockaddr_in6 *sin6 = nla_data(attr);
+
+ if (nla_len(attr) < sizeof(*sin6) ||
+ !inet_ntop(AF_INET6, &sin6->sin6_addr, host, sizeof(host)))
+ return;
+ if (sin6->sin6_scope_id)
+ snprintf(buf, buflen, "[%s%%%u]:%u", host,
+ sin6->sin6_scope_id, ntohs(sin6->sin6_port));
+ else
+ snprintf(buf, buflen, "[%s]:%u", host,
+ ntohs(sin6->sin6_port));
+ break;
+ }
+ }
+}
+
+static void parse_client_get(struct genlmsghdr *gnlh,
+ struct client_dump_ctx *ctx)
+{
+ struct nfsd_client client = {
+ .address = "unknown",
+ .state = UINT32_MAX,
+ .callback_state = UINT32_MAX,
+ };
+ struct nlattr *attr;
+ int rem;
+
+ nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), rem) {
+ switch (nla_type(attr)) {
+ case NFSD_A_CLIENT_CLIENTID:
+ client.clientid = nla_get_u64(attr);
+ break;
+ case NFSD_A_CLIENT_ADDRESS:
+ format_client_address(attr, client.address,
+ sizeof(client.address));
+ break;
+ case NFSD_A_CLIENT_MINOR_VERSION:
+ client.minor_version = nla_get_u32(attr);
+ break;
+ case NFSD_A_CLIENT_STATE:
+ client.state = nla_get_u32(attr);
+ break;
+ case NFSD_A_CLIENT_LEASE_REMAINING:
+ client.lease_remaining = nla_get_s64(attr);
+ break;
+ case NFSD_A_CLIENT_RECLAIM_COMPLETE:
+ client.reclaim_complete = true;
+ break;
+ case NFSD_A_CLIENT_CALLBACK_STATE:
+ client.callback_state = nla_get_u32(attr);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!ctx->header_printed) {
+ printf("%-18s %-47s %-4s %-11s %8s %7s %8s\n",
+ "clientid", "address", "vers", "state", "lease",
+ "reclaim", "callback");
+ ctx->header_printed = true;
+ }
+
+ printf("0x%016" PRIx64 " %-47s 4.%-2u %-11s %8" PRId64
+ " %7s %8s\n",
+ client.clientid, client.address, client.minor_version,
+ client_state_name(client.state), client.lease_remaining,
+ client.minor_version ?
+ (client.reclaim_complete ? "yes" : "no") : "-",
+ callback_state_name(client.callback_state));
+}
+
static void parse_version_get(struct genlmsghdr *gnlh)
{
struct nlattr *attr;
@@ -418,6 +561,9 @@ static int recv_handler(struct nl_msg *msg, void *arg)
case NFSD_CMD_POOL_MODE_GET:
parse_pool_mode_get(gnlh);
break;
+ case NFSD_CMD_CLIENT_GET:
+ parse_client_get(gnlh, arg);
+ break;
case LOCKD_CMD_SERVER_GET:
parse_lockd_get(gnlh);
break;
@@ -647,6 +793,72 @@ out:
return ret;
}
+static void clients_usage(void)
+{
+ printf("Usage: %s clients\n", taskname);
+ printf(" Display NFSv4 clients and their current state.\n");
+}
+
+static int clients_func(struct nl_sock *sock, int argc, char **argv)
+{
+ struct client_dump_ctx ctx = {};
+ struct genlmsghdr *ghdr;
+ struct nlmsghdr *nlh;
+ struct nl_msg *msg;
+ struct nl_cb *cb;
+ int opt, ret;
+
+ optind = 1;
+ while ((opt = getopt_long(argc, argv, "h", help_only_options, NULL)) != -1) {
+ switch (opt) {
+ case 'h':
+ clients_usage();
+ return 0;
+ }
+ }
+
+ if (!nfsd_nl_family_setup(sock))
+ return 1;
+
+ msg = netlink_msg_alloc(sock, nfsd_nl_family);
+ if (!msg)
+ return 1;
+
+ nlh = nlmsg_hdr(msg);
+ nlh->nlmsg_flags |= NLM_F_DUMP;
+ ghdr = nlmsg_data(nlh);
+ ghdr->cmd = NFSD_CMD_CLIENT_GET;
+
+ cb = nl_cb_alloc(NL_CB_CUSTOM);
+ if (!cb) {
+ xlog(L_ERROR, "failed to allocate netlink callbacks");
+ ret = 1;
+ goto out;
+ }
+
+ 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);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, recv_handler, &ctx);
+
+ while (ret > 0)
+ nl_recvmsgs(sock, cb);
+ if (ret < 0) {
+ xlog(L_ERROR, "Error: %s", strerror(-ret));
+ ret = 1;
+ }
+out_cb:
+ nl_cb_put(cb);
+out:
+ nlmsg_free(msg);
+ return ret;
+}
+
static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
int pool_count, int *pool_threads, char *scope, int minthreads,
uuid_t fh_key)
@@ -1950,6 +2162,7 @@ static int nlm_func(struct nl_sock *sock, int argc, char ** argv)
enum nfsdctl_commands {
NFSDCTL_STATUS,
+ NFSDCTL_CLIENTS,
NFSDCTL_THREADS,
NFSDCTL_VERSION,
NFSDCTL_LISTENER,
@@ -1962,6 +2175,8 @@ static int parse_command(char *str)
{
if (!strcmp(str, "status"))
return NFSDCTL_STATUS;
+ if (!strcmp(str, "clients"))
+ return NFSDCTL_CLIENTS;
if (!strcmp(str, "threads"))
return NFSDCTL_THREADS;
if (!strcmp(str, "version"))
@@ -1981,6 +2196,7 @@ typedef int (*nfsdctl_func)(struct nl_sock *sock, int argc, char **argv);
static nfsdctl_func func[] = {
[NFSDCTL_STATUS] = status_func,
+ [NFSDCTL_CLIENTS] = clients_func,
[NFSDCTL_THREADS] = threads_func,
[NFSDCTL_VERSION] = version_func,
[NFSDCTL_LISTENER] = listener_func,
@@ -2005,6 +2221,7 @@ static void usage(void)
printf(" threads get/set nfsd thread settings\n");
printf(" nlm get current nlm settings\n");
printf(" status get current RPC processing info\n");
+ printf(" clients get current NFSv4 client information\n");
printf(" autostart start server with settings from /etc/nfs.conf\n");
}
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v1 3/3] nfsdctl: display per-client NFSv4 state usage
2026-08-25 6:52 [PATCH v1 0/3] nfsdctl: list NFSv4 clients Prabhakar Pujeri
2026-08-25 6:52 ` [PATCH v1 1/3] nfsdctl: report NFSv4 grace state Prabhakar Pujeri
2026-08-25 6:52 ` [PATCH v1 2/3] nfsdctl: add a command to list NFSv4 clients Prabhakar Pujeri
@ 2026-08-25 6:52 ` Prabhakar Pujeri
2026-08-28 14:37 ` [PATCH v1 0/3] nfsdctl: list NFSv4 clients Jeff Layton
3 siblings, 0 replies; 6+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:52 UTC (permalink / raw)
To: steved; +Cc: cel, jlayton, linux-nfs, Prabhakar Pujeri
Extend the clients table with the session and stateid record counts
exported by NFSD. These totals help an operator identify which client is
retaining server-side state before using the detailed nfsd filesystem
views.
Display a dash when a count attribute is absent. This keeps the command
accurate when a new nfsdctl is used with a kernel that supports the base
client dump but not the count extension. Require the last count attribute
before selecting the system UAPI header so building against a header with
only the base command still uses the bundled copy.
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
configure.ac | 2 +-
support/include/nfsd_netlink.h | 5 ++++
utils/nfsdctl/nfsdctl.8 | 5 ++--
utils/nfsdctl/nfsdctl.adoc | 5 ++--
utils/nfsdctl/nfsdctl.c | 51 +++++++++++++++++++++++++++++++---
5 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/configure.ac b/configure.ac
index 66330d0..0b22523 100644
--- a/configure.ac
+++ b/configure.ac
@@ -259,7 +259,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_CLIENT_GET;]])],
+ [[int foo = NFSD_A_CLIENT_LAYOUT_STATEIDS;]])],
[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/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
index ae49538..49c9531 100644
--- a/support/include/nfsd_netlink.h
+++ b/support/include/nfsd_netlink.h
@@ -290,6 +290,11 @@ enum {
NFSD_A_CLIENT_LEASE_REMAINING,
NFSD_A_CLIENT_RECLAIM_COMPLETE,
NFSD_A_CLIENT_CALLBACK_STATE,
+ NFSD_A_CLIENT_SESSIONS,
+ NFSD_A_CLIENT_OPEN_STATEIDS,
+ NFSD_A_CLIENT_LOCK_STATEIDS,
+ NFSD_A_CLIENT_DELEGATION_STATEIDS,
+ NFSD_A_CLIENT_LAYOUT_STATEIDS,
__NFSD_A_CLIENT_MAX,
NFSD_A_CLIENT_MAX = (__NFSD_A_CLIENT_MAX - 1)
diff --git a/utils/nfsdctl/nfsdctl.8 b/utils/nfsdctl/nfsdctl.8
index 36af317..3850461 100644
--- a/utils/nfsdctl/nfsdctl.8
+++ b/utils/nfsdctl/nfsdctl.8
@@ -85,8 +85,9 @@ nfs.conf, 16 server threads will be brought online.
.RS 4
List NFSv4 clients known to the server. The output reports each client\(cqs
server-generated client ID, peer address, minor version, lease and callback
-states, lease time remaining, and reclaim status. This subcommand requires
-elevated privileges.
+states, lease time remaining, reclaim status, session count, and counts of
+open, lock, delegation, and layout stateids. This subcommand requires elevated
+privileges.
.RE
.sp
\fBlistener\fP
diff --git a/utils/nfsdctl/nfsdctl.adoc b/utils/nfsdctl/nfsdctl.adoc
index 3e70b99..55575de 100644
--- a/utils/nfsdctl/nfsdctl.adoc
+++ b/utils/nfsdctl/nfsdctl.adoc
@@ -50,8 +50,9 @@ Each subcommand can also accept its own set of options and arguments. The
List NFSv4 clients known to the server. The output reports each client's
server-generated client ID, peer address, minor version, lease and callback
- states, lease time remaining, and reclaim status. This subcommand requires
- elevated privileges.
+ states, lease time remaining, reclaim status, session count, and counts of
+ open, lock, delegation, and layout stateids. This subcommand requires elevated
+ privileges.
*listener*::
diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
index 7f12860..9e5505d 100644
--- a/utils/nfsdctl/nfsdctl.c
+++ b/utils/nfsdctl/nfsdctl.c
@@ -94,6 +94,11 @@ struct nfsd_client {
uint32_t minor_version;
uint32_t state;
uint32_t callback_state;
+ uint32_t sessions;
+ uint32_t open_stateids;
+ uint32_t lock_stateids;
+ uint32_t delegation_stateids;
+ uint32_t layout_stateids;
bool reclaim_complete;
};
@@ -290,6 +295,14 @@ static const char *callback_state_name(uint32_t state)
}
}
+static void format_client_count(uint32_t count, char *buf, size_t buflen)
+{
+ if (count == UINT32_MAX)
+ snprintf(buf, buflen, "%s", "-");
+ else
+ snprintf(buf, buflen, "%u", count);
+}
+
static void format_client_address(const struct nlattr *attr, char *buf,
size_t buflen)
{
@@ -334,7 +347,13 @@ static void parse_client_get(struct genlmsghdr *gnlh,
.address = "unknown",
.state = UINT32_MAX,
.callback_state = UINT32_MAX,
+ .sessions = UINT32_MAX,
+ .open_stateids = UINT32_MAX,
+ .lock_stateids = UINT32_MAX,
+ .delegation_stateids = UINT32_MAX,
+ .layout_stateids = UINT32_MAX,
};
+ char sessions[11], opens[11], locks[11], delegations[11], layouts[11];
struct nlattr *attr;
int rem;
@@ -363,22 +382,46 @@ static void parse_client_get(struct genlmsghdr *gnlh,
case NFSD_A_CLIENT_CALLBACK_STATE:
client.callback_state = nla_get_u32(attr);
break;
+ case NFSD_A_CLIENT_SESSIONS:
+ client.sessions = nla_get_u32(attr);
+ break;
+ case NFSD_A_CLIENT_OPEN_STATEIDS:
+ client.open_stateids = nla_get_u32(attr);
+ break;
+ case NFSD_A_CLIENT_LOCK_STATEIDS:
+ client.lock_stateids = nla_get_u32(attr);
+ break;
+ case NFSD_A_CLIENT_DELEGATION_STATEIDS:
+ client.delegation_stateids = nla_get_u32(attr);
+ break;
+ case NFSD_A_CLIENT_LAYOUT_STATEIDS:
+ client.layout_stateids = nla_get_u32(attr);
+ break;
default:
break;
}
}
+ format_client_count(client.sessions, sessions, sizeof(sessions));
+ format_client_count(client.open_stateids, opens, sizeof(opens));
+ format_client_count(client.lock_stateids, locks, sizeof(locks));
+ format_client_count(client.delegation_stateids, delegations,
+ sizeof(delegations));
+ format_client_count(client.layout_stateids, layouts, sizeof(layouts));
+
if (!ctx->header_printed) {
- printf("%-18s %-47s %-4s %-11s %8s %7s %8s\n",
+ printf("%-18s %-47s %-4s %-11s %8s %5s %5s %5s %5s %6s %7s %8s\n",
"clientid", "address", "vers", "state", "lease",
- "reclaim", "callback");
+ "sess", "open", "lock", "deleg", "layout", "reclaim",
+ "callback");
ctx->header_printed = true;
}
printf("0x%016" PRIx64 " %-47s 4.%-2u %-11s %8" PRId64
- " %7s %8s\n",
+ " %5s %5s %5s %5s %6s %7s %8s\n",
client.clientid, client.address, client.minor_version,
client_state_name(client.state), client.lease_remaining,
+ sessions, opens, locks, delegations, layouts,
client.minor_version ?
(client.reclaim_complete ? "yes" : "no") : "-",
callback_state_name(client.callback_state));
@@ -796,7 +839,7 @@ out:
static void clients_usage(void)
{
printf("Usage: %s clients\n", taskname);
- printf(" Display NFSv4 clients and their current state.\n");
+ printf(" Display NFSv4 clients and their current state usage.\n");
}
static int clients_func(struct nl_sock *sock, int argc, char **argv)
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread