* [PATCH v2 0/2] nfsdctl: list NFSv4 clients
@ 2026-08-31 9:27 Prabhakar Pujeri
2026-08-31 9:27 ` [PATCH v2 1/2] nfsdctl: add a command to " Prabhakar Pujeri
2026-08-31 9:27 ` [PATCH v2 2/2] nfsdctl: display per-client NFSv4 state usage Prabhakar Pujeri
0 siblings, 2 replies; 3+ messages in thread
From: Prabhakar Pujeri @ 2026-08-31 9:27 UTC (permalink / raw)
To: steved; +Cc: Prabhakar Pujeri, cel, jlayton, linux-nfs
Add the userspace consumer for the companion NFSD Netlink client-state v2
series, whose cover Message-ID is:
<cover.1787988917.git.prabhakar.pujeri@dell.com>
The first patch adds a privileged `nfsdctl clients` table for basic client
identity, address, lease, confirmation, reclaim, and callback state. The
second adds session and stateid record counts.
Attribute presence remains the compatibility boundary. Missing count
attributes are displayed as dashes. The bundled UAPI header permits building
before the new kernel header is installed, while the configure probe selects
a system header only when it contains the complete count extension.
Changes since v1:
- decode separate IPv4/IPv6 address, port, and scope-ID attributes instead
of raw sockaddr data
- detect NLM_F_DUMP_INTR on client records and NLMSG_DONE, and fail with a
retry diagnostic rather than displaying a potentially inconsistent list
- decode variable-width 32-bit or 64-bit uint count attributes and display
counts as uint64_t values
- drop the companion grace-status patch with the kernel-side grace patch
Validation performed:
- strict checkpatch on both patches: no errors, warnings, or checks
- full nfs-utils build and `make check` using the bundled UAPI header: no
failures or errors; one environment-dependent test skipped
- full build and `make check` against headers_install from the final kernel:
no failures or errors; one environment-dependent test skipped
- bundled and kernel-source NFSD UAPI headers compared byte-for-byte
- two independent two-vCPU QEMU boots of the exact companion kernel
- each `nfsdctl clients` run displayed the expected NFSv4.2 client,
loopback peer, lease, callback/reclaim state, one session, one open,
one lock, one delegation, and zero layouts
- nfsdctl output agreed with raw Netlink and /proc/fs/nfsd/clients state
Development assistance: an LLM assisted with review analysis, implementation,
commit-message drafting, and test-harness development.
Assisted-by: LLM
v1: https://lore.kernel.org/r/cover.1787639206.git.prabhakar.pujeri@dell.com
Prabhakar Pujeri (2):
nfsdctl: add a command to list NFSv4 clients
nfsdctl: display per-client NFSv4 state usage
configure.ac | 2 +-
support/include/nfsd_netlink.h | 43 +++++
utils/nfsdctl/nfsdctl.8 | 21 +++
utils/nfsdctl/nfsdctl.adoc | 14 ++
utils/nfsdctl/nfsdctl.c | 310 +++++++++++++++++++++++++++++++++
5 files changed, 389 insertions(+), 1 deletion(-)
base-commit: 67ed1bdb1af1c70a5fd3b377a997401b7676b827
--
2.54.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/2] nfsdctl: add a command to list NFSv4 clients
2026-08-31 9:27 [PATCH v2 0/2] nfsdctl: list NFSv4 clients Prabhakar Pujeri
@ 2026-08-31 9:27 ` Prabhakar Pujeri
2026-08-31 9:27 ` [PATCH v2 2/2] nfsdctl: display per-client NFSv4 state usage Prabhakar Pujeri
1 sibling, 0 replies; 3+ messages in thread
From: Prabhakar Pujeri @ 2026-08-31 9:27 UTC (permalink / raw)
To: steved; +Cc: Prabhakar Pujeri, cel, jlayton, linux-nfs
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.
Decode the peer from separate IPv4 or IPv6 address, port, and scope-ID
attributes. Keep unknown enum values visible as "unknown" so newer
kernels remain usable with this client.
Detect NLM_F_DUMP_INTR on both client records and NLMSG_DONE. Report an
error instead of silently presenting a client list that may have skipped
or repeated a record.
Update the bundled UAPI header and require NFSD_CMD_CLIENT_GET before
selecting a system copy of that header.
Assisted-by: LLM
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
Changes since v1:
- decode separate address, port, and scope-ID attributes
- reject interrupted dumps and ask the administrator to retry
v1: https://lore.kernel.org/r/b595db7a74fb55fc4c6b3b200c7970e4bb06007b.1787639206.git.prabhakar.pujeri@dell.com
configure.ac | 2 +-
support/include/nfsd_netlink.h | 38 +++++
utils/nfsdctl/nfsdctl.8 | 19 +++
utils/nfsdctl/nfsdctl.adoc | 12 ++
utils/nfsdctl/nfsdctl.c | 255 +++++++++++++++++++++++++++++++++
5 files changed, 325 insertions(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 2ce1e12..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_CMD_SERVER_STATS_GET;]])],
+ [[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 87da1d0..715acb4 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,
@@ -260,6 +280,23 @@ 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_ADDRESS4,
+ NFSD_A_CLIENT_ADDRESS6,
+ NFSD_A_CLIENT_ADDRESS_PORT,
+ NFSD_A_CLIENT_ADDRESS_SCOPE_ID,
+ 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,
@@ -280,6 +317,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 1f526e7..18a7a56 100644
--- a/utils/nfsdctl/nfsdctl.8
+++ b/utils/nfsdctl/nfsdctl.8
@@ -81,6 +81,15 @@ 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. If the client set changes in a way that interrupts the
+dump, the command reports an error and should be retried.
+.RE
+.sp
\fBlistener\fP
.RS 4
Get/set the listening sockets for the server. Run this without arguments to
@@ -259,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 e85348e..d227562 100644
--- a/utils/nfsdctl/nfsdctl.adoc
+++ b/utils/nfsdctl/nfsdctl.adoc
@@ -46,6 +46,14 @@ 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. If the client set changes in a way that interrupts the
+ dump, the command reports an error and should be retried.
+
*listener*::
Get/set the listening sockets for the server. Run this without arguments to
@@ -138,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 c712674..c0a6d5f 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,31 @@ struct server_socket {
bool active;
};
+#define MAX_CLIENT_ADDRESS_LEN 80
+
+struct nfsd_client {
+ char address[MAX_CLIENT_ADDRESS_LEN];
+ struct in_addr address4;
+ struct in6_addr address6;
+ uint64_t clientid;
+ int64_t lease_remaining;
+ uint32_t address_scope_id;
+ uint32_t minor_version;
+ uint32_t state;
+ uint32_t callback_state;
+ uint16_t address_port;
+ bool have_address4;
+ bool have_address6;
+ bool have_address_port;
+ bool reclaim_complete;
+};
+
+struct client_dump_ctx {
+ int *ret;
+ bool header_printed;
+ bool dump_interrupted;
+};
+
#define MAX_NFSD_SOCKETS 256
int nfsd_socket_count;
@@ -184,6 +210,16 @@ static int finish_handler(struct nl_msg *msg, void *arg)
return NL_SKIP;
}
+static int client_finish_handler(struct nl_msg *msg, void *arg)
+{
+ struct client_dump_ctx *ctx = arg;
+
+ if (nlmsg_hdr(msg)->nlmsg_flags & NLM_F_DUMP_INTR)
+ ctx->dump_interrupted = true;
+ *ctx->ret = 0;
+ return NL_SKIP;
+}
+
static int ack_handler(struct nl_msg *msg, void *arg)
{
int *ret = arg;
@@ -241,6 +277,145 @@ 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 nfsd_client *client, char *buf,
+ size_t buflen)
+{
+ char host[INET6_ADDRSTRLEN];
+
+ snprintf(buf, buflen, "%s", "unknown");
+ if (!client->have_address_port ||
+ client->have_address4 == client->have_address6)
+ return;
+
+ if (client->have_address4) {
+ if (!inet_ntop(AF_INET, &client->address4, host, sizeof(host)))
+ return;
+ snprintf(buf, buflen, "%s:%u", host,
+ ntohs(client->address_port));
+ return;
+ }
+
+ if (!inet_ntop(AF_INET6, &client->address6, host, sizeof(host)))
+ return;
+ if (client->address_scope_id)
+ snprintf(buf, buflen, "[%s%%%u]:%u", host,
+ client->address_scope_id, ntohs(client->address_port));
+ else
+ snprintf(buf, buflen, "[%s]:%u", host,
+ ntohs(client->address_port));
+}
+
+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_ADDRESS4:
+ if (nla_len(attr) == sizeof(client.address4)) {
+ memcpy(&client.address4, nla_data(attr),
+ sizeof(client.address4));
+ client.have_address4 = true;
+ }
+ break;
+ case NFSD_A_CLIENT_ADDRESS6:
+ if (nla_len(attr) == sizeof(client.address6)) {
+ memcpy(&client.address6, nla_data(attr),
+ sizeof(client.address6));
+ client.have_address6 = true;
+ }
+ break;
+ case NFSD_A_CLIENT_ADDRESS_PORT:
+ if (nla_len(attr) == sizeof(client.address_port)) {
+ memcpy(&client.address_port, nla_data(attr),
+ sizeof(client.address_port));
+ client.have_address_port = true;
+ }
+ break;
+ case NFSD_A_CLIENT_ADDRESS_SCOPE_ID:
+ if (nla_len(attr) == sizeof(client.address_scope_id))
+ client.address_scope_id = nla_get_u32(attr);
+ 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;
+ }
+ }
+ format_client_address(&client, client.address, sizeof(client.address));
+
+ 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;
@@ -410,6 +585,11 @@ 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:
+ if (nlmsg_hdr(msg)->nlmsg_flags & NLM_F_DUMP_INTR)
+ ((struct client_dump_ctx *)arg)->dump_interrupted = true;
+ parse_client_get(gnlh, arg);
+ break;
case LOCKD_CMD_SERVER_GET:
parse_lockd_get(gnlh);
break;
@@ -639,6 +819,76 @@ 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;
+ ctx.ret = &ret;
+ nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
+ nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, client_finish_handler, &ctx);
+ 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;
+ } else if (ctx.dump_interrupted) {
+ xlog(L_ERROR, "NFSv4 client list changed during dump; retry");
+ 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)
@@ -1942,6 +2192,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,
@@ -1954,6 +2205,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"))
@@ -1973,6 +2226,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,
@@ -1997,6 +2251,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] 3+ messages in thread
* [PATCH v2 2/2] nfsdctl: display per-client NFSv4 state usage
2026-08-31 9:27 [PATCH v2 0/2] nfsdctl: list NFSv4 clients Prabhakar Pujeri
2026-08-31 9:27 ` [PATCH v2 1/2] nfsdctl: add a command to " Prabhakar Pujeri
@ 2026-08-31 9:27 ` Prabhakar Pujeri
1 sibling, 0 replies; 3+ messages in thread
From: Prabhakar Pujeri @ 2026-08-31 9:27 UTC (permalink / raw)
To: steved; +Cc: Prabhakar Pujeri, cel, jlayton, linux-nfs
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.
Decode both four-byte and eight-byte representations of the Netlink uint
attributes and display the complete u64 value. Display a dash when a
count attribute is absent or malformed. 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.
Assisted-by: LLM
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
Changes since v1:
- decode 32-bit and 64-bit variable-width uint count attributes
- store and format all counts as uint64_t values
v1: https://lore.kernel.org/r/05399eaee964c52a935f65b2c793f77cb8a2a97a.1787639206.git.prabhakar.pujeri@dell.com
configure.ac | 2 +-
support/include/nfsd_netlink.h | 5 +++
utils/nfsdctl/nfsdctl.8 | 8 +++--
utils/nfsdctl/nfsdctl.adoc | 8 +++--
utils/nfsdctl/nfsdctl.c | 63 +++++++++++++++++++++++++++++++---
5 files changed, 75 insertions(+), 11 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 715acb4..d977833 100644
--- a/support/include/nfsd_netlink.h
+++ b/support/include/nfsd_netlink.h
@@ -292,6 +292,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 18a7a56..e4e823e 100644
--- a/utils/nfsdctl/nfsdctl.8
+++ b/utils/nfsdctl/nfsdctl.8
@@ -85,9 +85,11 @@ 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. If the client set changes in a way that interrupts the
-dump, the command reports an error and should be retried.
+states, lease time remaining, reclaim status, session count, and counts of
+open, lock, delegation, and layout stateids. This subcommand requires elevated
+privileges.
+If the client set changes in a way that interrupts the dump, the command
+reports an error and should be retried.
.RE
.sp
\fBlistener\fP
diff --git a/utils/nfsdctl/nfsdctl.adoc b/utils/nfsdctl/nfsdctl.adoc
index d227562..a25dc0a 100644
--- a/utils/nfsdctl/nfsdctl.adoc
+++ b/utils/nfsdctl/nfsdctl.adoc
@@ -50,9 +50,11 @@ 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. If the client set changes in a way that interrupts the
- dump, the command reports an error and should be retried.
+ states, lease time remaining, reclaim status, session count, and counts of
+ open, lock, delegation, and layout stateids. This subcommand requires elevated
+ privileges.
+ If the client set changes in a way that interrupts the dump, the command
+ reports an error and should be retried.
*listener*::
diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
index c0a6d5f..096714f 100644
--- a/utils/nfsdctl/nfsdctl.c
+++ b/utils/nfsdctl/nfsdctl.c
@@ -101,6 +101,11 @@ struct nfsd_client {
bool have_address4;
bool have_address6;
bool have_address_port;
+ uint64_t sessions;
+ uint64_t open_stateids;
+ uint64_t lock_stateids;
+ uint64_t delegation_stateids;
+ uint64_t layout_stateids;
bool reclaim_complete;
};
@@ -309,6 +314,26 @@ static const char *callback_state_name(uint32_t state)
}
}
+static void parse_client_uint(const struct nlattr *attr, uint64_t *value)
+{
+ switch (nla_len(attr)) {
+ case sizeof(uint32_t):
+ *value = nla_get_u32(attr);
+ break;
+ case sizeof(uint64_t):
+ *value = nla_get_u64(attr);
+ break;
+ }
+}
+
+static void format_client_count(uint64_t count, char *buf, size_t buflen)
+{
+ if (count == UINT64_MAX)
+ snprintf(buf, buflen, "%s", "-");
+ else
+ snprintf(buf, buflen, "%" PRIu64, count);
+}
+
static void format_client_address(const struct nfsd_client *client, char *buf,
size_t buflen)
{
@@ -344,7 +369,13 @@ static void parse_client_get(struct genlmsghdr *gnlh,
.address = "unknown",
.state = UINT32_MAX,
.callback_state = UINT32_MAX,
+ .sessions = UINT64_MAX,
+ .open_stateids = UINT64_MAX,
+ .lock_stateids = UINT64_MAX,
+ .delegation_stateids = UINT64_MAX,
+ .layout_stateids = UINT64_MAX,
};
+ char sessions[21], opens[21], locks[21], delegations[21], layouts[21];
struct nlattr *attr;
int rem;
@@ -394,23 +425,47 @@ 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:
+ parse_client_uint(attr, &client.sessions);
+ break;
+ case NFSD_A_CLIENT_OPEN_STATEIDS:
+ parse_client_uint(attr, &client.open_stateids);
+ break;
+ case NFSD_A_CLIENT_LOCK_STATEIDS:
+ parse_client_uint(attr, &client.lock_stateids);
+ break;
+ case NFSD_A_CLIENT_DELEGATION_STATEIDS:
+ parse_client_uint(attr, &client.delegation_stateids);
+ break;
+ case NFSD_A_CLIENT_LAYOUT_STATEIDS:
+ parse_client_uint(attr, &client.layout_stateids);
+ break;
default:
break;
}
}
format_client_address(&client, client.address, sizeof(client.address));
+ 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));
@@ -822,7 +877,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] 3+ messages in thread
end of thread, other threads:[~2026-08-31 9:28 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 9:27 [PATCH v2 0/2] nfsdctl: list NFSv4 clients Prabhakar Pujeri
2026-08-31 9:27 ` [PATCH v2 1/2] nfsdctl: add a command to " Prabhakar Pujeri
2026-08-31 9:27 ` [PATCH v2 2/2] nfsdctl: display per-client NFSv4 state usage Prabhakar Pujeri
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.