* [PATCH 2/7] ksmbd: add SMB3 request replay support
2026-07-16 9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
@ 2026-07-16 9:57 ` Namjae Jeon
2026-07-16 9:57 ` [PATCH 3/7] ksmbd: fix malformed procfs status output Namjae Jeon
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-16 9:57 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
SMB3 clients can replay selected requests after a channel disconnect by
setting SMB2_FLAGS_REPLAY_OPERATION. The command sequence window rejects
duplicate MessageIds on one connection, but it does not validate requests
resent on another channel with a new MessageId.
Add the state and validation required to replay durable CREATE and
file-handle operations:
- track each open ChannelSequence, outstanding request counts, and lock
sequence entries;
- retain a request-owned open reference until the common response path
completes ChannelSequence accounting;
- replay DurableHandleReqV2 CREATE requests by CreateGuid, validating the
durable state, SecurityContext, session, lease key, and persistent flag;
- publish CreateGuid and SecurityContext before an oplock or lease break can
defer CREATE, rejecting replays of that pending CREATE with
STATUS_FILE_NOT_AVAILABLE;
- retain the original CREATE action and replay completed CreateGuid requests,
including requests that did not receive a durable-handle grant, without
modifying the existing open;
- make replayed oplock and lease break acknowledgements idempotent; and
- preserve SMB2_FLAGS_REPLAY_OPERATION in responses.
Return STATUS_FILE_NOT_AVAILABLE when ChannelSequence validation rejects a
replayed WRITE, IOCTL, or SET_INFO request.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/ksmbd_work.c | 2 +
fs/smb/server/ksmbd_work.h | 9 +
fs/smb/server/mgmt/user_session.c | 8 -
fs/smb/server/oplock.c | 38 ++-
fs/smb/server/oplock.h | 2 +-
fs/smb/server/server.c | 9 +-
fs/smb/server/smb2pdu.c | 525 ++++++++++++++++++++++++++++--
fs/smb/server/smb2pdu.h | 1 +
fs/smb/server/vfs_cache.c | 13 +-
fs/smb/server/vfs_cache.h | 18 +
10 files changed, 586 insertions(+), 39 deletions(-)
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index c3f8915c3952..f35335307670 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -11,6 +11,7 @@
#include "server.h"
#include "connection.h"
#include "ksmbd_work.h"
+#include "vfs_cache.h"
#include "mgmt/ksmbd_ida.h"
static struct kmem_cache *work_cache;
@@ -88,6 +89,7 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
ksmbd_release_id(&work->conn->async_ida, work->async_id);
if (work->owns_conn_ref)
ksmbd_conn_put(work->conn);
+ ksmbd_fd_put(work, work->request_open);
kmem_cache_free(work_cache, work);
}
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index e35a40d764d6..52d0c4dee65c 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -12,6 +12,7 @@
struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
+struct ksmbd_file;
#define KSMBD_WORK_INLINE_IOVS 4
@@ -93,6 +94,7 @@ struct ksmbd_work {
/* Work owns a reference to @conn. */
bool owns_conn_ref:1;
bool need_invalidate_rkey:1;
+ bool request_open_chseq_tracked:1;
unsigned int remote_key;
/* cancel works */
@@ -100,6 +102,13 @@ struct ksmbd_work {
void **cancel_argv;
void (*cancel_fn)(void **argv);
+ /*
+ * Refcounted open associated with the SMB2 command currently being
+ * processed.
+ */
+ struct ksmbd_file *request_open;
+ __le16 request_open_chseq;
+
struct work_struct work;
/* List head at conn->requests */
struct list_head request_entry;
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 5b9bd46ff3a8..cbe00f00f3f6 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -478,14 +478,6 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
down_write(&conn->session_lock);
xa_for_each(&conn->sessions, id, sess) {
- unsigned long chann_id;
- struct channel *chann;
-
- xa_for_each(&sess->ksmbd_chann_list, chann_id, chann) {
- if (chann->conn != conn)
- ksmbd_conn_set_exiting(chann->conn);
- }
-
ksmbd_chann_del(conn, sess);
if (xa_empty(&sess->ksmbd_chann_list)) {
xa_erase(&conn->sessions, sess->id);
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 74bd2fadc757..1949bb794b5e 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1203,6 +1203,18 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+
+ /*
+ * Keep a conflicting CREATE asynchronous while waiting for an
+ * oplock-break acknowledgement. Besides avoiding a blocked client
+ * request, this lets a replay arrive while the original CREATE is
+ * still pending and be rejected with FILE_NOT_AVAILABLE.
+ */
+ if (in_work) {
+ setup_async_work(in_work, NULL, NULL);
+ smb2_send_interim_resp(in_work, STATUS_PENDING);
+ release_async_work(in_work);
+ }
}
err = smb2_oplock_break_noti(brk_opinfo);
@@ -1450,7 +1462,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
*/
int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
struct ksmbd_file *fp, __u16 tid,
- struct lease_ctx_info *lctx, int share_ret)
+ struct lease_ctx_info *lctx, int share_ret, bool replay)
{
int err = 0;
int break_level = SMB2_OPLOCK_LEVEL_II;
@@ -1535,6 +1547,21 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
prev_op_has_lease = prev_opinfo->is_lease;
if (prev_op_has_lease)
prev_op_state = prev_opinfo->o_lease->state;
+ /*
+ * A replay received while this open is waiting for an oplock or lease
+ * break must not observe an intermediate level and proceed as a new
+ * open. This check has to precede break_needed. an oplock may already
+ * have been downgraded from Batch to II while its acknowledgement is
+ * still pending.
+ */
+ if (replay &&
+ (test_bit(0, &prev_opinfo->pending_break) ||
+ prev_opinfo->op_state == OPLOCK_ACK_WAIT)) {
+ err = -EINPROGRESS;
+ opinfo_put(prev_opinfo);
+ goto err_out;
+ }
+
if (share_ret < 0 &&
prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
err = share_ret;
@@ -1569,7 +1596,14 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto set_lev;
}
if (err == -ENOENT) {
- if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
+ /*
+ * A pending durable CREATE can lose the previous oplock when
+ * its holder closes the file. In that case grant the original
+ * request its full caching state. Other opens still need the
+ * normal shared-open downgrade below.
+ */
+ if (!prev_durable_open &&
+ req_op_level != SMB2_OPLOCK_LEVEL_NONE)
req_op_level = SMB2_OPLOCK_LEVEL_II;
goto set_lev;
}
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index ee1550f5c177..23274b645ede 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -96,7 +96,7 @@ struct oplock_break_info {
int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
u64 pid, struct ksmbd_file *fp, __u16 tid,
- struct lease_ctx_info *lctx, int share_ret);
+ struct lease_ctx_info *lctx, int share_ret, bool replay);
void smb_break_all_levII_oplock(struct ksmbd_work *work,
struct ksmbd_file *fp, int is_trunc);
void smb_break_all_levII_oplock_rename(struct ksmbd_work *work, struct ksmbd_file *fp);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index f5baba934840..b0e9689fd3c8 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -15,6 +15,7 @@
#include "server.h"
#include "smb_common.h"
+#include "smb2pdu.h"
#include "../common/smb2status.h"
#include "connection.h"
#include "transport_ipc.h"
@@ -221,8 +222,10 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
}
rc = __process_request(work, conn, &command);
- if (rc == SERVER_HANDLER_ABORT)
+ if (rc == SERVER_HANDLER_ABORT) {
+ smb2_complete_request_open(work);
break;
+ }
/*
* Call smb2_set_rsp_credits() function to set number of credits
@@ -235,10 +238,13 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (rc < 0) {
conn->ops->set_rsp_status(work,
STATUS_INVALID_PARAMETER);
+ smb2_complete_request_open(work);
goto send;
}
}
+ smb2_complete_request_open(work);
+
is_chained = is_chained_smb2_message(work);
if (work->sess &&
@@ -254,6 +260,7 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
} while (is_chained == true);
send:
+ smb2_complete_request_open(work);
/*
* Release any credit charge still outstanding for this request. On
* the normal path smb2_set_rsp_credits() already returned it, but the
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index e09c60191dc2..602b60417174 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -570,6 +570,8 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
SMB2_FLAGS_RELATED_OPERATIONS);
+ if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
+ rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@@ -662,6 +664,8 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+ if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
+ rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@@ -672,6 +676,185 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
return 0;
}
+static __le16 smb3_hdr_channel_sequence(struct smb2_hdr *hdr)
+{
+ return ((struct smb3_hdr_req *)hdr)->ChannelSequence;
+}
+
+static bool smb3_hdr_replay(struct smb2_hdr *hdr)
+{
+ return hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION;
+}
+
+static int smb3_verify_channel_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct smb2_hdr *hdr,
+ bool allow_stale)
+{
+ __le16 chseq_le;
+ u16 chseq, old_chseq;
+ int ret = 0;
+
+ if (work->conn->dialect < SMB30_PROT_ID)
+ return 0;
+
+ chseq_le = smb3_hdr_channel_sequence(hdr);
+ chseq = le16_to_cpu(chseq_le);
+
+ spin_lock(&fp->f_lock);
+ old_chseq = le16_to_cpu(fp->channel_sequence);
+ if (smb3_hdr_replay(hdr)) {
+ if (chseq == old_chseq && fp->outstanding_pre_requests == 0) {
+ fp->outstanding_requests++;
+ } else if ((u16)(chseq - old_chseq) <= 0x7fff &&
+ fp->outstanding_pre_requests == 0) {
+ fp->outstanding_pre_requests += fp->outstanding_requests;
+ fp->outstanding_requests = 1;
+ fp->channel_sequence = chseq_le;
+ } else if (allow_stale) {
+ fp->outstanding_pre_requests++;
+ } else {
+ ret = -EAGAIN;
+ }
+ } else {
+ if (chseq == old_chseq) {
+ fp->outstanding_requests++;
+ } else if ((u16)(chseq - old_chseq) <= 0x7fff) {
+ fp->outstanding_pre_requests += fp->outstanding_requests;
+ fp->outstanding_requests = 1;
+ fp->channel_sequence = chseq_le;
+ } else if (allow_stale) {
+ fp->outstanding_pre_requests++;
+ } else {
+ ret = -EAGAIN;
+ }
+ }
+ spin_unlock(&fp->f_lock);
+
+ return ret;
+}
+
+static void smb3_complete_channel_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ __le16 chseq_le)
+{
+ u16 chseq;
+
+ if (work->conn->dialect < SMB30_PROT_ID)
+ return;
+
+ chseq = le16_to_cpu(chseq_le);
+
+ spin_lock(&fp->f_lock);
+ if (chseq == le16_to_cpu(fp->channel_sequence)) {
+ if (fp->outstanding_requests)
+ fp->outstanding_requests--;
+ } else {
+ if (fp->outstanding_pre_requests)
+ fp->outstanding_pre_requests--;
+ }
+ spin_unlock(&fp->f_lock);
+}
+
+static int smb2_set_request_open(struct ksmbd_work *work, struct ksmbd_file *fp,
+ struct smb2_hdr *hdr, bool verify_chseq,
+ bool allow_stale_chseq)
+{
+ struct ksmbd_file *open;
+ int ret;
+
+ smb2_complete_request_open(work);
+
+ open = ksmbd_file_get(fp);
+ if (!open)
+ return -ESTALE;
+
+ if (verify_chseq) {
+ ret = smb3_verify_channel_sequence(work, fp, hdr,
+ allow_stale_chseq);
+ if (ret) {
+ ksmbd_fd_put(work, open);
+ return ret;
+ }
+ work->request_open_chseq_tracked = true;
+ }
+
+ work->request_open = open;
+ work->request_open_chseq = smb3_hdr_channel_sequence(hdr);
+ return 0;
+}
+
+void smb2_complete_request_open(struct ksmbd_work *work)
+{
+ struct ksmbd_file *open = work->request_open;
+
+ if (!open)
+ return;
+
+ if (work->request_open_chseq_tracked)
+ smb3_complete_channel_sequence(work, open,
+ work->request_open_chseq);
+
+ work->request_open = NULL;
+ work->request_open_chseq_tracked = false;
+ ksmbd_fd_put(work, open);
+}
+
+static bool smb2_lock_sequence_applicable(struct ksmbd_work *work,
+ struct ksmbd_file *fp)
+{
+ return fp->is_resilient || fp->is_durable || fp->is_persistent ||
+ (work->conn->dialect >= SMB30_PROT_ID &&
+ (work->conn->cli_cap & SMB2_GLOBAL_CAP_MULTI_CHANNEL));
+}
+
+static void smb2_verify_lock_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct smb2_lock_req *req)
+{
+ u32 val, index;
+ u8 sequence;
+
+ if (work->conn->dialect == SMB20_PROT_ID ||
+ !smb2_lock_sequence_applicable(work, fp))
+ return;
+
+ val = le32_to_cpu(req->LockSequenceNumber);
+ sequence = val & 0xf;
+ index = val >> 4;
+ if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
+ return;
+
+ spin_lock(&fp->f_lock);
+ if (fp->lock_seq[index - 1].valid &&
+ fp->lock_seq[index - 1].sequence != sequence)
+ fp->lock_seq[index - 1].valid = false;
+ spin_unlock(&fp->f_lock);
+}
+
+static void smb2_update_lock_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct smb2_lock_req *req)
+{
+ u32 val, index;
+ u8 sequence;
+
+ if (work->conn->dialect == SMB20_PROT_ID ||
+ !smb2_lock_sequence_applicable(work, fp))
+ return;
+
+ val = le32_to_cpu(req->LockSequenceNumber);
+ sequence = val & 0xf;
+ index = val >> 4;
+ if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
+ return;
+
+ spin_lock(&fp->f_lock);
+ fp->lock_seq[index - 1].valid = true;
+ fp->lock_seq[index - 1].sequence = sequence;
+ spin_unlock(&fp->f_lock);
+}
+
/**
* smb2_allocate_rsp_buf() - allocate smb2 response buffer
* @work: smb work containing smb request buffer
@@ -3164,12 +3347,86 @@ struct durable_info {
unsigned short int type;
bool persistent;
bool reconnected;
+ bool replay;
+ bool replay_consumed;
bool app_instance_id;
unsigned int timeout;
char *CreateGuid;
char AppInstanceId[SMB2_CREATE_GUID_SIZE];
};
+static int smb2_check_durable_replay(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct lease_ctx_info *lc,
+ bool persistent)
+{
+ struct oplock_info *opinfo;
+ int ret = 0;
+
+ if (!fp->is_durable && !fp->is_persistent)
+ return -EACCES;
+
+ if (ksmbd_vfs_compare_durable_owner(fp, work->sess->user) == false)
+ return -EACCES;
+
+ if (fp->is_persistent && !persistent)
+ return -EINVAL;
+
+ opinfo = opinfo_get(fp);
+ if (!opinfo)
+ return 0;
+
+ if (opinfo->sess && opinfo->sess->id != work->sess->id) {
+ ret = -ENOEXEC;
+ goto out;
+ }
+
+ if (opinfo->is_lease) {
+ if (!lc ||
+ memcmp(opinfo->o_lease->lease_key, lc->lease_key,
+ SMB2_LEASE_KEY_SIZE)) {
+ ret = -EACCES;
+ goto out;
+ }
+ } else {
+ if (lc) {
+ ret = -EACCES;
+ goto out;
+ }
+
+ if (fp->is_durable && opinfo->level != SMB2_OPLOCK_LEVEL_BATCH)
+ ret = -EACCES;
+ }
+out:
+ opinfo_put(opinfo);
+ return ret;
+}
+
+static bool smb2_durable_replay_consumed(struct ksmbd_file *fp)
+{
+ bool consumed;
+
+ spin_lock(&fp->f_lock);
+ consumed = fp->durable_replay_consumed;
+ spin_unlock(&fp->f_lock);
+
+ return consumed;
+}
+
+static void smb2_mark_durable_replay_consumed(struct ksmbd_file *fp)
+{
+ spin_lock(&fp->f_lock);
+ fp->durable_replay_consumed = true;
+ spin_unlock(&fp->f_lock);
+}
+
+static bool smb2_durable_replay_differs(struct ksmbd_file *fp,
+ struct smb2_create_req *req)
+{
+ return fp->cdoption != req->CreateDisposition ||
+ fp->create_file_attributes != req->FileAttributes;
+}
+
static int parse_durable_handle_context(struct ksmbd_work *work,
struct smb2_create_req *req,
struct lease_ctx_info *lc,
@@ -3297,6 +3554,10 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
durable_v2_blob =
(struct create_durable_req_v2 *)context;
ksmbd_debug(SMB, "Request for durable v2 open\n");
+ dh_info->CreateGuid = durable_v2_blob->dcontext.CreateGuid;
+ dh_info->persistent =
+ le32_to_cpu(durable_v2_blob->dcontext.Flags) &
+ SMB2_DHANDLE_FLAG_PERSISTENT;
dh_info->fp = ksmbd_lookup_fd_cguid(durable_v2_blob->dcontext.CreateGuid);
if (dh_info->fp) {
if (!memcmp(conn->ClientGUID, dh_info->fp->client_guid,
@@ -3307,12 +3568,73 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
- if (dh_info->fp->conn) {
+ if (dh_info->fp->f_state == FP_NEW) {
+ /* Original CREATE is still pending. */
+ ksmbd_put_durable_fd(dh_info->fp);
+ err = -EAGAIN;
+ goto out;
+ }
+
+ if (!dh_info->fp->is_durable &&
+ !dh_info->fp->is_persistent) {
+ /*
+ * A DurableHandleReqV2 CREATE can complete
+ * without granting durability (for example, if
+ * it requested no oplock). Its CreateGuid still
+ * identifies a completed CREATE for replay.
+ */
+ if (dh_info->fp->conn &&
+ ksmbd_vfs_compare_durable_owner(
+ dh_info->fp, work->sess->user)) {
+ if (smb2_durable_replay_consumed(
+ dh_info->fp)) {
+ ksmbd_put_durable_fd(dh_info->fp);
+ dh_info->fp = NULL;
+ dh_info->type = dh_idx;
+ dh_info->replay_consumed = true;
+ break;
+ }
+ if (smb2_durable_replay_differs(
+ dh_info->fp, req))
+ smb2_mark_durable_replay_consumed(
+ dh_info->fp);
+ dh_info->replay = true;
+ dh_info->type = dh_idx;
+ goto out;
+ }
+ ksmbd_put_durable_fd(dh_info->fp);
+ err = -EACCES;
+ goto out;
+ }
+
+ if (dh_info->fp->conn &&
+ smb2_durable_replay_consumed(dh_info->fp)) {
+ ksmbd_put_durable_fd(dh_info->fp);
+ dh_info->fp = NULL;
+ dh_info->type = dh_idx;
+ dh_info->replay_consumed = true;
+ break;
+ }
+
+ err = smb2_check_durable_replay(work,
+ dh_info->fp,
+ lc,
+ dh_info->persistent);
+ if (err) {
ksmbd_put_durable_fd(dh_info->fp);
- err = -EBADF;
goto out;
}
- dh_info->reconnected = true;
+
+ if (dh_info->fp->conn) {
+ if (smb2_durable_replay_differs(dh_info->fp,
+ req))
+ smb2_mark_durable_replay_consumed(
+ dh_info->fp);
+ dh_info->replay = true;
+ } else {
+ dh_info->reconnected = true;
+ }
+ dh_info->type = dh_idx;
goto out;
}
ksmbd_put_durable_fd(dh_info->fp);
@@ -3321,10 +3643,6 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
if ((lc && (lc->req_state & SMB2_LEASE_HANDLE_CACHING_LE)) ||
req_op_level == SMB2_OPLOCK_LEVEL_BATCH) {
- dh_info->CreateGuid =
- durable_v2_blob->dcontext.CreateGuid;
- dh_info->persistent =
- le32_to_cpu(durable_v2_blob->dcontext.Flags);
dh_info->timeout =
le32_to_cpu(durable_v2_blob->dcontext.Timeout);
dh_info->type = dh_idx;
@@ -3407,6 +3725,7 @@ int smb2_open(struct ksmbd_work *work)
int contxt_cnt = 0, query_disk_id = 0;
bool maximal_access_ctxt = false, posix_ctxt = false;
bool aapl_ctxt = false;
+ bool durable_rsp = true;
__u64 aapl_req_bitmap = 0, aapl_client_caps = 0;
int s_type = 0;
int next_off = 0;
@@ -3526,6 +3845,21 @@ int smb2_open(struct ksmbd_work *work)
if (rc)
goto err_out2;
+ if (dh_info.replay == true) {
+ fp = dh_info.fp;
+ if (ksmbd_override_fsids(work)) {
+ rc = -ENOMEM;
+ goto err_out2;
+ }
+
+ file_info = FILE_OPENED;
+ rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat);
+ if (rc)
+ goto err_out2;
+
+ goto reconnected_fp;
+ }
+
if (dh_info.reconnected == true) {
rc = smb2_check_durable_oplock(conn, share, dh_info.fp,
lc, sess->user, name);
@@ -3950,7 +4284,23 @@ int smb2_open(struct ksmbd_work *work)
goto err_out;
}
+ /*
+ * Publish the client and create GUID before an oplock/lease break can
+ * make this CREATE pending. A replay of that in-flight CREATE must find
+ * this FP_NEW handle and fail with STATUS_FILE_NOT_AVAILABLE instead of
+ * waiting on the same break again.
+ */
+ memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+ if (dh_info.CreateGuid) {
+ memcpy(fp->create_guid, dh_info.CreateGuid, SMB2_CREATE_GUID_SIZE);
+ fp->durable_replay_consumed = dh_info.replay_consumed;
+ rc = ksmbd_vfs_set_durable_owner(fp, sess->user);
+ if (rc)
+ goto err_out;
+ }
+
fp->cdoption = req->CreateDisposition;
+ fp->create_file_attributes = req->FileAttributes;
fp->daccess = daccess;
fp->saccess = req->ShareAccess;
fp->coption = req->CreateOptions;
@@ -4118,7 +4468,8 @@ int smb2_open(struct ksmbd_work *work)
rc = smb_grant_oplock(work, req_op_level,
fp->persistent_id, fp,
le32_to_cpu(req->hdr.Id.SyncId.TreeId),
- lc, share_ret);
+ lc, share_ret,
+ smb3_hdr_replay(&req->hdr));
if (rc < 0)
goto err_out1;
}
@@ -4262,7 +4613,7 @@ int smb2_open(struct ksmbd_work *work)
if (created)
smb2_new_xattrs(tcon, &path, fp);
- memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+ fp->create_action = cpu_to_le32(file_info);
if (dh_info.type == DURABLE_REQ_V2 || dh_info.type == DURABLE_REQ) {
if (dh_info.type == DURABLE_REQ_V2 && dh_info.persistent &&
@@ -4271,10 +4622,7 @@ int smb2_open(struct ksmbd_work *work)
fp->is_persistent = true;
else
fp->is_durable = true;
-
if (dh_info.type == DURABLE_REQ_V2) {
- memcpy(fp->create_guid, dh_info.CreateGuid,
- SMB2_CREATE_GUID_SIZE);
if (dh_info.app_instance_id)
memcpy(fp->app_instance_id,
dh_info.AppInstanceId,
@@ -4295,10 +4643,22 @@ int smb2_open(struct ksmbd_work *work)
* cares, it sends its own AAPL context on this same CREATE, which
* this function's normal (non-reconnect) parsing already handles.
*/
-reconnected_fp:
+ reconnected_fp:
+ if (dh_info.replay)
+ file_info = le32_to_cpu(fp->create_action);
rsp->StructureSize = cpu_to_le16(89);
opinfo = opinfo_get(fp);
rsp->OplockLevel = opinfo != NULL ? opinfo->level : 0;
+ /*
+ * A durable CREATE replay does not modify the existing open. When
+ * replayed without an oplock, however, its response reflects that
+ * request and cannot include a new durable-handle response context.
+ */
+ if (dh_info.replay && !lc &&
+ req_op_level == SMB2_OPLOCK_LEVEL_NONE) {
+ rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+ durable_rsp = false;
+ }
rsp->Flags = 0;
rsp->CreateAction = cpu_to_le32(file_info);
rsp->CreationTime = cpu_to_le64(fp->create_time);
@@ -4399,7 +4759,8 @@ int smb2_open(struct ksmbd_work *work)
next_off = conn->vals->create_disk_id_size;
}
- if (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2) {
+ if (durable_rsp &&
+ (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2)) {
struct create_context *durable_ccontext;
durable_ccontext = (struct create_context *)(rsp->Buffer +
@@ -4490,8 +4851,11 @@ int smb2_open(struct ksmbd_work *work)
err_out2:
if (!rc) {
- rc = ksmbd_update_fstate(&work->sess->file_table, fp,
- FP_INITED);
+ if (!dh_info.replay)
+ rc = ksmbd_update_fstate(&work->sess->file_table, fp,
+ FP_INITED);
+ if (!rc)
+ rc = smb2_set_request_open(work, fp, &req->hdr, false, false);
if (!rc)
rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);
}
@@ -4523,15 +4887,22 @@ int smb2_open(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
else if (rc == -EMFILE)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ else if (rc == -EINPROGRESS)
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ else if (rc == -EAGAIN)
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
if (!rsp->hdr.Status)
rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
- if (fp)
+ if (fp && !dh_info.replay)
ksmbd_fd_put(work, fp);
smb2_set_err_rsp(work);
ksmbd_debug(SMB, "Error response: %x\n", rsp->hdr.Status);
}
+ if (dh_info.replay)
+ ksmbd_put_durable_fd(dh_info.fp);
+
if (dh_info.reconnected) {
/*
* If reconnect succeeded, fp was republished in the
@@ -7669,6 +8040,7 @@ int smb2_set_info(struct ksmbd_work *work)
struct smb2_set_info_rsp *rsp;
struct ksmbd_file *fp = NULL;
int rc = 0;
+ bool chseq_err = false;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
ksmbd_debug(SMB, "Received smb2 set info request\n");
@@ -7708,6 +8080,13 @@ int smb2_set_info(struct ksmbd_work *work)
goto err_out;
}
+ rc = smb2_set_request_open(work, fp, &req->hdr, true, false);
+ if (rc) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ chseq_err = true;
+ goto err_out;
+ }
+
saved_cred = override_creds(fp->filp->f_cred);
switch (req->InfoType) {
case SMB2_O_INFO_FILE:
@@ -7758,7 +8137,7 @@ int smb2_set_info(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
else if (rc == -EBUSY || rc == -ENOTEMPTY)
rsp->hdr.Status = STATUS_DIRECTORY_NOT_EMPTY;
- else if (rc == -EAGAIN)
+ else if (rc == -EAGAIN && !chseq_err)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (rc == -EBADF || rc == -ESTALE)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
@@ -7967,6 +8346,10 @@ int smb2_read(struct ksmbd_work *work)
goto out;
}
+ err = smb2_set_request_open(work, fp, &req->hdr, true, true);
+ if (err)
+ goto out;
+
if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
pr_err("Not permitted to read : 0x%x\n", fp->daccess);
err = -EACCES;
@@ -8214,6 +8597,7 @@ int smb2_write(struct ksmbd_work *work)
char *data_buf;
bool writethrough = false, is_rdma_channel = false;
bool async_interim = false;
+ bool chseq_err = false;
int err = 0;
unsigned int max_write_size = work->conn->vals->max_write_size;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
@@ -8303,6 +8687,13 @@ int smb2_write(struct ksmbd_work *work)
goto out;
}
+ err = smb2_set_request_open(work, fp, &req->hdr, true, false);
+ if (err) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ chseq_err = true;
+ goto out;
+ }
+
if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
pr_err("Not permitted to write : 0x%x\n", fp->daccess);
err = -EACCES;
@@ -8374,7 +8765,7 @@ int smb2_write(struct ksmbd_work *work)
if (async_interim)
release_async_work(work);
- if (err == -EAGAIN)
+ if (err == -EAGAIN && !chseq_err)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (err == -ENOSPC || err == -EFBIG)
rsp->hdr.Status = STATUS_DISK_FULL;
@@ -8386,7 +8777,7 @@ int smb2_write(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_SHARING_VIOLATION;
else if (err == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
- else
+ else if (rsp->hdr.Status == 0)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
smb2_set_err_rsp(work);
@@ -8675,6 +9066,12 @@ int smb2_lock(struct ksmbd_work *work)
goto out2;
}
+ err = smb2_set_request_open(work, fp, &req->hdr, false, false);
+ if (err)
+ goto out2;
+
+ smb2_verify_lock_sequence(work, fp, req);
+
filp = fp->filp;
lock_count = le16_to_cpu(req->LockCount);
lock_ele = req->locks;
@@ -8959,6 +9356,7 @@ int smb2_lock(struct ksmbd_work *work)
err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lock_rsp));
if (err)
goto out;
+ smb2_update_lock_sequence(work, fp, req);
ksmbd_fd_put(work, fp);
return 0;
@@ -9509,10 +9907,12 @@ int smb2_ioctl(struct ksmbd_work *work)
struct smb2_ioctl_req *req;
struct smb2_ioctl_rsp *rsp;
unsigned int cnt_code, nbytes = 0, out_buf_len, in_buf_len;
- u64 id = KSMBD_NO_FID;
+ u64 id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
struct ksmbd_conn *conn = work->conn;
int ret = 0;
char *buffer;
+ bool no_fileid_ioctl = false;
+ bool chseq_err = false;
ksmbd_debug(SMB, "Received smb2 ioctl request\n");
@@ -9525,14 +9925,17 @@ int smb2_ioctl(struct ksmbd_work *work)
ksmbd_debug(SMB, "Compound request set FID = %llu\n",
work->compound_fid);
id = work->compound_fid;
+ pid = work->compound_pfid;
}
} else {
req = smb_get_msg(work->request_buf);
rsp = smb_get_msg(work->response_buf);
}
- if (!has_file_id(id))
+ if (!has_file_id(id)) {
id = req->VolatileFileId;
+ pid = req->PersistentFileId;
+ }
if (req->Flags != cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL)) {
ret = -EOPNOTSUPP;
@@ -9542,6 +9945,40 @@ int smb2_ioctl(struct ksmbd_work *work)
buffer = (char *)req + le32_to_cpu(req->InputOffset);
cnt_code = le32_to_cpu(req->CtlCode);
+ switch (cnt_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ case FSCTL_DFS_GET_REFERRALS_EX:
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ case FSCTL_PIPE_WAIT:
+ no_fileid_ioctl = true;
+ break;
+ default:
+ break;
+ }
+
+ if (!no_fileid_ioctl && has_file_id(id)) {
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
+ if (!fp) {
+ if (cnt_code == FSCTL_DUPLICATE_EXTENTS_TO_FILE) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ goto out2;
+ }
+ ret = -ENOENT;
+ goto out;
+ }
+
+ ret = smb2_set_request_open(work, fp, &req->hdr, true, false);
+ ksmbd_fd_put(work, fp);
+ if (ret) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ chseq_err = true;
+ goto out;
+ }
+ }
+
ret = smb2_calc_max_out_buf_len(work,
offsetof(struct smb2_ioctl_rsp, Buffer),
le32_to_cpu(req->MaxOutputResponse));
@@ -10077,7 +10514,7 @@ int smb2_ioctl(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
else if (ret == -ENOSPC)
rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
- else if (ret < 0 || rsp->hdr.Status == 0)
+ else if (!chseq_err && (ret < 0 || rsp->hdr.Status == 0))
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
out2:
@@ -10117,6 +10554,14 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
return;
}
+ ret = smb2_set_request_open(work, fp, &req->hdr, false, false);
+ if (ret) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return;
+ }
+
opinfo = opinfo_get(fp);
if (!opinfo) {
pr_err("unexpected null oplock_info\n");
@@ -10129,6 +10574,22 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
if (opinfo->op_state != OPLOCK_ACK_WAIT) {
ksmbd_debug(SMB, "unexpected oplock state 0x%x\n",
opinfo->op_state);
+ if (smb3_hdr_replay(&req->hdr) &&
+ opinfo->op_state == OPLOCK_STATE_NONE) {
+ rsp->StructureSize = cpu_to_le16(24);
+ rsp->OplockLevel = opinfo->level;
+ rsp->Reserved = 0;
+ rsp->Reserved2 = 0;
+ rsp->VolatileFid = volatile_id;
+ rsp->PersistentFid = persistent_id;
+ ret = ksmbd_iov_pin_rsp(work, rsp,
+ sizeof(struct smb2_oplock_break));
+ if (ret)
+ ksmbd_debug(SMB,
+ "failed to pin replayed oplock break response: %d\n",
+ ret);
+ goto out_no_state_change;
+ }
if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE)
status = STATUS_INVALID_OPLOCK_PROTOCOL;
else
@@ -10197,6 +10658,7 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
out:
opinfo->op_state = OPLOCK_STATE_NONE;
wake_up_interruptible_all(&opinfo->oplock_q);
+out_no_state_change:
opinfo_put(opinfo);
ksmbd_fd_put(work, fp);
}
@@ -10249,11 +10711,15 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
if (opinfo->op_state == OPLOCK_STATE_NONE) {
pr_err("unexpected lease break state 0x%x\n",
opinfo->op_state);
+ if (smb3_hdr_replay(&req->hdr))
+ goto replay_rsp;
rsp->hdr.Status = STATUS_UNSUCCESSFUL;
goto err_out;
}
if (!atomic_read(&opinfo->breaking_cnt)) {
+ if (smb3_hdr_replay(&req->hdr))
+ goto replay_rsp;
rsp->hdr.Status = STATUS_UNSUCCESSFUL;
goto err_out;
}
@@ -10288,6 +10754,19 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
opinfo_put(opinfo);
return;
+replay_rsp:
+ rsp->StructureSize = cpu_to_le16(36);
+ rsp->Reserved = 0;
+ rsp->Flags = 0;
+ memcpy(rsp->LeaseKey, req->LeaseKey, 16);
+ rsp->LeaseState = lease->state;
+ rsp->LeaseDuration = 0;
+ ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lease_ack));
+ if (ret)
+ goto err_out;
+ opinfo_put(opinfo);
+ return;
+
err_out:
smb2_set_err_rsp(work);
opinfo_put(opinfo);
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index d9fed26dbfcd..02b0f46ac1be 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -432,6 +432,7 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn);
/* smb2 misc functions */
int ksmbd_smb2_check_message(struct ksmbd_work *work);
+void smb2_complete_request_open(struct ksmbd_work *work);
/* smb2 command handlers */
int smb2_handle_negotiate(struct ksmbd_work *work);
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 55a1cc5b909c..c0dbb5ef3bcd 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -729,6 +729,11 @@ static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
return fp;
}
+struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp)
+{
+ return ksmbd_fp_get(fp);
+}
+
static struct ksmbd_file *__ksmbd_lookup_fd(struct ksmbd_file_table *ft,
u64 id)
{
@@ -1570,7 +1575,7 @@ void ksmbd_stop_durable_scavenger(void)
}
/*
- * ksmbd_vfs_copy_durable_owner - Copy owner info for durable reconnect
+ * ksmbd_vfs_set_durable_owner - Store owner info for durable replay/reconnect
* @fp: ksmbd file pointer to store owner info
* @user: user pointer to copy from
*
@@ -1579,8 +1584,8 @@ void ksmbd_stop_durable_scavenger(void)
*
* Return: 0 on success, or negative error code on failure
*/
-static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
- struct ksmbd_user *user)
+int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
+ struct ksmbd_user *user)
{
char *name;
@@ -1651,7 +1656,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
if (WARN_ON_ONCE(!fp->conn))
return false;
- if (ksmbd_vfs_copy_durable_owner(fp, user))
+ if (ksmbd_vfs_set_durable_owner(fp, user))
return false;
/*
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 1d9edc906b54..5aff9bb556ec 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -82,6 +82,13 @@ struct durable_owner {
char *name;
};
+#define KSMBD_LOCK_SEQ_ARRAY_SIZE 64
+
+struct ksmbd_lock_sequence {
+ bool valid;
+ u8 sequence;
+};
+
struct ksmbd_file {
struct file *filp;
u64 persistent_id;
@@ -101,6 +108,7 @@ struct ksmbd_file {
__le32 saccess;
__le32 coption;
__le32 cdoption;
+ __le32 create_file_attributes;
__u64 create_time;
__u64 change_time;
__u64 allocation_size;
@@ -128,6 +136,8 @@ struct ksmbd_file {
unsigned int durable_timeout;
unsigned int durable_scavenger_timeout;
+ /* CREATE action returned when this durable handle was established. */
+ __le32 create_action;
/* if ls is happening on directory, below is valid*/
struct ksmbd_readdir_data readdir_data;
@@ -139,9 +149,14 @@ struct ksmbd_file {
bool is_persistent;
bool is_resilient;
bool durable_reconnect_disabled;
+ bool durable_replay_consumed;
bool is_posix_ctxt;
struct durable_owner owner;
+ __le16 channel_sequence;
+ unsigned int outstanding_requests;
+ unsigned int outstanding_pre_requests;
+ struct ksmbd_lock_sequence lock_seq[KSMBD_LOCK_SEQ_ARRAY_SIZE];
/*
* Pending CHANGE_NOTIFY completions for this handle, sent with
@@ -180,6 +195,9 @@ struct ksmbd_file *ksmbd_lookup_fd_fast(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_foreign_fd(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
u64 pid);
+int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
+ struct ksmbd_user *user);
+struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp);
void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
void ksmbd_inode_put(struct ksmbd_inode *ci);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 3/7] ksmbd: fix malformed procfs status output
2026-07-16 9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
2026-07-16 9:57 ` [PATCH 2/7] ksmbd: add SMB3 request replay support Namjae Jeon
@ 2026-07-16 9:57 ` Namjae Jeon
2026-07-16 9:57 ` [PATCH 4/7] ksmbd: expose connection runtime state in procfs Namjae Jeon
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-16 9:57 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
The ksmbd procfs monitoring files produce misleading or malformed output.
The constant-name helper uses a bitwise test for enum values. This omits
zero-valued constants and can print multiple names for one lease state. It
also unconditionally emits a newline, splitting entries in the open-file
table across two lines. Session capabilities are printed as numeric flag
values even though a table of descriptive names is available.
Use exact matching for enum values. Print flag names as a comma-separated
list, preserving unknown bits as hexadecimal values. Let callers control
line termination so each open-file entry remains on one line. Print common
session properties once, and report signing and encryption independently.
Adjust client and open-file column widths for IPv6 addresses and 64-bit
file IDs, and fix the misspelled OPLOCK_EXCLUSIVE name. Also expose and
maintain the total request count alongside the per-command counters.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/connection.c | 12 ++---
fs/smb/server/mgmt/user_session.c | 75 +++++++++++++------------------
fs/smb/server/misc.h | 7 +--
fs/smb/server/proc.c | 38 ++++++++++++++++
fs/smb/server/stats.h | 4 +-
fs/smb/server/vfs_cache.c | 16 ++++---
6 files changed, 89 insertions(+), 63 deletions(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 3d2b8f243a22..47f6d561e150 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -33,9 +33,9 @@ static int proc_show_clients(struct seq_file *m, void *v)
struct timespec64 now, t;
int i;
- seq_printf(m, "#%-20s %-10s %-10s %-10s %-10s %-10s\n",
- "<name>", "<dialect>", "<credits>", "<open files>",
- "<requests>", "<last active>");
+ seq_printf(m, "#%-40s %-10s %-10s %-12s %-10s %s\n",
+ "<client>", "<dialect>", "<credits>", "<open files>",
+ "<requests>", "<last active>");
down_read(&conn_list_lock);
hash_for_each(conn_list, i, conn, hlist) {
@@ -44,11 +44,11 @@ static int proc_show_clients(struct seq_file *m, void *v)
t = timespec64_sub(now, t);
#if IS_ENABLED(CONFIG_IPV6)
if (!conn->inet_addr)
- seq_printf(m, "%-20pI6c", &conn->inet6_addr);
+ seq_printf(m, " %-40pI6c", &conn->inet6_addr);
else
#endif
- seq_printf(m, "%-20pI4", &conn->inet_addr);
- seq_printf(m, " 0x%-10x %-10u %-12d %-10d %ptT\n",
+ seq_printf(m, " %-40pI4", &conn->inet_addr);
+ seq_printf(m, " 0x%-8x %-10u %-12d %-10d %ptT\n",
conn->dialect,
conn->total_credits,
atomic_read(&conn->stats.open_files_count),
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index cbe00f00f3f6..09c944a67141 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -90,9 +90,15 @@ static int show_proc_session(struct seq_file *m, void *v)
sess = (struct ksmbd_session *)m->private;
ksmbd_user_session_get(sess);
+ seq_printf(m, "%-20s\t%s\n", "user", session_user_name(sess));
+ seq_printf(m, "%-20s\t%llu\n", "id", sess->id);
+ seq_printf(m, "%-20s\t%s\n", "state", session_state_string(sess));
+
i = 0;
down_read(&sess->chann_lock);
xa_for_each(&sess->ksmbd_chann_list, id, chan) {
+ const char *name;
+
#if IS_ENABLED(CONFIG_IPV6)
if (chan->conn->inet_addr)
seq_printf(m, "%-20s\t%pI4\n", "client",
@@ -104,29 +110,37 @@ static int show_proc_session(struct seq_file *m, void *v)
seq_printf(m, "%-20s\t%pI4\n", "client",
&chan->conn->inet_addr);
#endif
- seq_printf(m, "%-20s\t%s\n", "user", session_user_name(sess));
- seq_printf(m, "%-20s\t%llu\n", "id", sess->id);
- seq_printf(m, "%-20s\t%s\n", "state",
- session_state_string(sess));
-
seq_printf(m, "%-20s\t", "capabilities");
ksmbd_proc_show_flag_names(m,
ksmbd_sess_cap_const_names,
ARRAY_SIZE(ksmbd_sess_cap_const_names),
chan->conn->vals->req_capabilities);
+ seq_putc(m, '\n');
if (sess->sign) {
- seq_printf(m, "%-20s\t", "signing");
- ksmbd_proc_show_const_name(m, "%s\t",
- ksmbd_signing_const_names,
- ARRAY_SIZE(ksmbd_signing_const_names),
- le16_to_cpu(chan->conn->signing_algorithm));
- } else if (sess->enc) {
- seq_printf(m, "%-20s\t", "encryption");
- ksmbd_proc_show_const_name(m, "%s\t",
- ksmbd_cipher_const_names,
- ARRAY_SIZE(ksmbd_cipher_const_names),
- le16_to_cpu(chan->conn->cipher_type));
+ unsigned int algorithm =
+ le16_to_cpu(chan->conn->signing_algorithm);
+
+ name = ksmbd_proc_const_name(ksmbd_signing_const_names,
+ ARRAY_SIZE(ksmbd_signing_const_names),
+ algorithm);
+ if (name)
+ seq_printf(m, "%-20s\t%s\n", "signing", name);
+ else
+ seq_printf(m, "%-20s\t0x%04x\n", "signing",
+ algorithm);
+ }
+ if (sess->enc) {
+ unsigned int cipher = le16_to_cpu(chan->conn->cipher_type);
+
+ name = ksmbd_proc_const_name(ksmbd_cipher_const_names,
+ ARRAY_SIZE(ksmbd_cipher_const_names),
+ cipher);
+ if (name)
+ seq_printf(m, "%-20s\t%s\n", "encryption", name);
+ else
+ seq_printf(m, "%-20s\t0x%04x\n", "encryption",
+ cipher);
}
i++;
}
@@ -152,35 +166,6 @@ static int show_proc_session(struct seq_file *m, void *v)
return 0;
}
-void ksmbd_proc_show_flag_names(struct seq_file *m,
- const struct ksmbd_const_name *table,
- int count,
- unsigned int flags)
-{
- int i;
-
- for (i = 0; i < count; i++) {
- if (table[i].const_value & flags)
- seq_printf(m, "0x%08x\t", table[i].const_value);
- }
- seq_putc(m, '\n');
-}
-
-void ksmbd_proc_show_const_name(struct seq_file *m,
- const char *format,
- const struct ksmbd_const_name *table,
- int count,
- unsigned int const_value)
-{
- int i;
-
- for (i = 0; i < count; i++) {
- if (table[i].const_value & const_value)
- seq_printf(m, format, table[i].name);
- }
- seq_putc(m, '\n');
-}
-
static int create_proc_session(struct ksmbd_session *sess)
{
char name[30];
diff --git a/fs/smb/server/misc.h b/fs/smb/server/misc.h
index 3909104e18ad..680375a966c5 100644
--- a/fs/smb/server/misc.h
+++ b/fs/smb/server/misc.h
@@ -53,11 +53,8 @@ void ksmbd_proc_show_flag_names(struct seq_file *m,
const struct ksmbd_const_name *table,
int count,
unsigned int flags);
-void ksmbd_proc_show_const_name(struct seq_file *m,
- const char *format,
- const struct ksmbd_const_name *table,
- int count,
- unsigned int const_value);
+const char *ksmbd_proc_const_name(const struct ksmbd_const_name *table,
+ int count, unsigned int const_value);
#else
static inline void ksmbd_proc_init(void) {}
static inline void ksmbd_proc_cleanup(void) {}
diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c
index 101a2cc45a44..b41490142480 100644
--- a/fs/smb/server/proc.c
+++ b/fs/smb/server/proc.c
@@ -27,6 +27,42 @@ struct proc_dir_entry *ksmbd_proc_create(const char *name,
show, v);
}
+void ksmbd_proc_show_flag_names(struct seq_file *m,
+ const struct ksmbd_const_name *table,
+ int count, unsigned int flags)
+{
+ unsigned int remaining = flags;
+ bool separator = false;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ unsigned int flag = table[i].const_value;
+
+ if (!flag || (remaining & flag) != flag)
+ continue;
+ seq_printf(m, "%s%s", separator ? "," : "", table[i].name);
+ separator = true;
+ remaining &= ~flag;
+ }
+
+ if (remaining)
+ seq_printf(m, "%s0x%08x", separator ? "," : "", remaining);
+ else if (!separator)
+ seq_puts(m, "none");
+}
+
+const char *ksmbd_proc_const_name(const struct ksmbd_const_name *table,
+ int count, unsigned int const_value)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (table[i].const_value == const_value)
+ return table[i].name;
+ }
+ return NULL;
+}
+
struct ksmbd_const_smb2_process_req {
unsigned int const_value;
const char *name;
@@ -71,6 +107,8 @@ static int proc_show_ksmbd_stats(struct seq_file *m, void *v)
ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS));
seq_printf(m, "tree connects: %lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS));
+ seq_printf(m, "requests: %lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_REQUESTS));
seq_printf(m, "read bytes: %lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_READ_BYTES));
seq_printf(m, "written bytes: %lld\n",
diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h
index b60c30c69077..08ee66f91eaa 100644
--- a/fs/smb/server/stats.h
+++ b/fs/smb/server/stats.h
@@ -52,8 +52,10 @@ static inline void ksmbd_counter_sub(int type, s64 value)
static inline void ksmbd_counter_inc_reqs(unsigned int cmd)
{
- if (cmd < KSMBD_COUNTER_MAX_REQS)
+ if (cmd < KSMBD_COUNTER_MAX_REQS) {
+ percpu_counter_inc(&ksmbd_counters.counters[KSMBD_COUNTER_REQUESTS]);
percpu_counter_inc(&ksmbd_counters.counters[KSMBD_COUNTER_FIRST_REQ + cmd]);
+ }
}
static inline s64 ksmbd_counter_sum(int type)
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index c0dbb5ef3bcd..c867cf32ae9c 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -66,7 +66,7 @@ static const struct ksmbd_const_name ksmbd_lease_const_names[] = {
static const struct ksmbd_const_name ksmbd_oplock_const_names[] = {
{SMB2_OPLOCK_LEVEL_NONE, "OPLOCK_NONE"},
{SMB2_OPLOCK_LEVEL_II, "OPLOCK_II"},
- {SMB2_OPLOCK_LEVEL_EXCLUSIVE, "OPLOCK_EXECL"},
+ {SMB2_OPLOCK_LEVEL_EXCLUSIVE, "OPLOCK_EXCLUSIVE"},
{SMB2_OPLOCK_LEVEL_BATCH, "OPLOCK_BATCH"},
};
@@ -76,14 +76,14 @@ static int proc_show_files(struct seq_file *m, void *v)
unsigned int id;
struct oplock_info *opinfo;
- seq_printf(m, "#%-10s %-10s %-10s %-10s %-15s %-10s %-10s %s\n",
+ seq_printf(m, "#%-10s %-18s %-18s %-10s %-16s %-10s %-10s %s\n",
"<tree id>", "<pid>", "<vid>", "<refcnt>",
"<oplock>", "<daccess>", "<saccess>",
"<name>");
read_lock(&global_ft.lock);
idr_for_each_entry(global_ft.idr, fp, id) {
- seq_printf(m, "%#-10x %#-10llx %#-10llx %#-10x",
+ seq_printf(m, " %#-10x %#-18llx %#-18llx %#-10x",
fp->tcon ? fp->tcon->id : 0,
fp->persistent_id,
fp->volatile_id,
@@ -93,6 +93,7 @@ static int proc_show_files(struct seq_file *m, void *v)
opinfo = rcu_dereference(fp->f_opinfo);
if (opinfo) {
const struct ksmbd_const_name *const_names;
+ const char *name;
int count;
unsigned int level;
@@ -106,11 +107,14 @@ static int proc_show_files(struct seq_file *m, void *v)
level = opinfo->level;
}
rcu_read_unlock();
- ksmbd_proc_show_const_name(m, " %-15s",
- const_names, count, level);
+ name = ksmbd_proc_const_name(const_names, count, level);
+ if (name)
+ seq_printf(m, " %-16s", name);
+ else
+ seq_printf(m, " 0x%-14x", level);
} else {
rcu_read_unlock();
- seq_printf(m, " %-15s", " ");
+ seq_printf(m, " %-16s", " ");
}
seq_printf(m, " %#010x %#010x %s\n",
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 4/7] ksmbd: expose connection runtime state in procfs
2026-07-16 9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
2026-07-16 9:57 ` [PATCH 2/7] ksmbd: add SMB3 request replay support Namjae Jeon
2026-07-16 9:57 ` [PATCH 3/7] ksmbd: fix malformed procfs status output Namjae Jeon
@ 2026-07-16 9:57 ` Namjae Jeon
2026-07-16 9:57 ` [PATCH 5/7] ksmbd: report session and open file details " Namjae Jeon
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-16 9:57 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
The clients proc file currently shows only a small subset of the state
needed to diagnose stalled or mis-negotiated connections.
Report the transport, connection state, outstanding and total credits,
session count, lifetime request count, and negotiated signing, encryption,
compression, and POSIX features. Report each connection as a key/value
record rather than a wide fixed-width table.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/connection.c | 96 +++++++++++++++++++++++++++++++++-----
1 file changed, 84 insertions(+), 12 deletions(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 47f6d561e150..26e312fb6da8 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -27,33 +27,105 @@ DECLARE_RWSEM(conn_list_lock);
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *proc_clients;
+static const char *ksmbd_conn_state_string(struct ksmbd_conn *conn)
+{
+ switch (READ_ONCE(conn->status)) {
+ case KSMBD_SESS_NEW:
+ return "new";
+ case KSMBD_SESS_GOOD:
+ return "good";
+ case KSMBD_SESS_EXITING:
+ return "exiting";
+ case KSMBD_SESS_NEED_RECONNECT:
+ return "reconnect";
+ case KSMBD_SESS_NEED_NEGOTIATE:
+ return "negotiate";
+ case KSMBD_SESS_NEED_SETUP:
+ return "setup";
+ case KSMBD_SESS_RELEASING:
+ return "releasing";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *ksmbd_conn_transport_string(struct ksmbd_conn *conn)
+{
+ if (conn->transport->ops->rdma_read || conn->transport->ops->rdma_write)
+ return "smbdirect";
+ return "tcp";
+}
+
+static void proc_show_conn_feature(struct seq_file *m, bool *separator,
+ bool enabled, const char *name)
+{
+ if (!enabled)
+ return;
+ seq_printf(m, "%s%s", *separator ? "," : "", name);
+ *separator = true;
+}
+
+static void proc_show_conn_features(struct seq_file *m,
+ struct ksmbd_conn *conn)
+{
+ bool separator = false;
+
+ proc_show_conn_feature(m, &separator,
+ conn->sign || conn->signing_negotiated, "sign");
+ proc_show_conn_feature(m, &separator, conn->cipher_type, "encrypt");
+ proc_show_conn_feature(m, &separator,
+ conn->compress_algorithm != SMB3_COMPRESS_NONE,
+ "compress");
+ proc_show_conn_feature(m, &separator, conn->posix_ext_supported, "posix");
+ if (!separator)
+ seq_puts(m, "none");
+}
+
static int proc_show_clients(struct seq_file *m, void *v)
{
struct ksmbd_conn *conn;
struct timespec64 now, t;
int i;
- seq_printf(m, "#%-40s %-10s %-10s %-12s %-10s %s\n",
- "<client>", "<dialect>", "<credits>", "<open files>",
- "<requests>", "<last active>");
-
down_read(&conn_list_lock);
hash_for_each(conn_list, i, conn, hlist) {
+ unsigned int outstanding_credits, total_credits;
+ unsigned long id;
+ void *entry;
+ unsigned int sessions = 0;
+
jiffies_to_timespec64(jiffies - conn->last_active, &t);
ktime_get_real_ts64(&now);
t = timespec64_sub(now, t);
+
+ spin_lock(&conn->credits_lock);
+ outstanding_credits = conn->outstanding_credits;
+ total_credits = conn->total_credits;
+ spin_unlock(&conn->credits_lock);
+
+ rcu_read_lock();
+ xa_for_each(&conn->sessions, id, entry)
+ sessions++;
+ rcu_read_unlock();
#if IS_ENABLED(CONFIG_IPV6)
if (!conn->inet_addr)
- seq_printf(m, " %-40pI6c", &conn->inet6_addr);
+ seq_printf(m, "client:\t%pI6c\n", &conn->inet6_addr);
else
#endif
- seq_printf(m, " %-40pI4", &conn->inet_addr);
- seq_printf(m, " 0x%-8x %-10u %-12d %-10d %ptT\n",
- conn->dialect,
- conn->total_credits,
- atomic_read(&conn->stats.open_files_count),
- atomic_read(&conn->req_running),
- &t);
+ seq_printf(m, "client:\t%pI4\n", &conn->inet_addr);
+ seq_printf(m, "transport:\t%s\n", ksmbd_conn_transport_string(conn));
+ seq_printf(m, "state:\t%s\n", ksmbd_conn_state_string(conn));
+ seq_printf(m, "dialect:\t0x%04x\n", conn->dialect);
+ seq_printf(m, "credits:\t%u/%u\n", outstanding_credits,
+ total_credits);
+ seq_printf(m, "sessions:\t%u\n", sessions);
+ seq_printf(m, "open_files:\t%d\n",
+ atomic_read(&conn->stats.open_files_count));
+ seq_printf(m, "requests:\t%lld\n",
+ atomic64_read(&conn->stats.request_served));
+ seq_puts(m, "features:\t");
+ proc_show_conn_features(m, conn);
+ seq_printf(m, "\nlast_active:\t%ptT\n\n", &t);
}
up_read(&conn_list_lock);
return 0;
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 5/7] ksmbd: report session and open file details in procfs
2026-07-16 9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
` (2 preceding siblings ...)
2026-07-16 9:57 ` [PATCH 4/7] ksmbd: expose connection runtime state in procfs Namjae Jeon
@ 2026-07-16 9:57 ` Namjae Jeon
2026-07-16 9:57 ` [PATCH 6/7] ksmbd: add procfs monitoring for active shares Namjae Jeon
2026-07-16 9:57 ` [PATCH 7/7] ksmbd: extend procfs server statistics Namjae Jeon
5 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-16 9:57 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
Session and file proc entries lack the state needed to correlate inactive
sessions with durable or delete-pending opens.
Add the account type, dialect, idle time, open-file count, tree-connect
count, and per-channel POSIX negotiation state to session entries. Extend
the open-file table with the file state, durable timeout, create options,
share access, and descriptive flags for durable, persistent, resilient,
delete-on-close, stream, POSIX, and attribute-only opens.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/mgmt/user_session.c | 82 ++++++++++++++++++++----------
fs/smb/server/vfs_cache.c | 84 +++++++++++++++++++++++++------
2 files changed, 123 insertions(+), 43 deletions(-)
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 09c944a67141..b2bc8119984f 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -78,6 +78,28 @@ static const char *session_user_name(struct ksmbd_session *session)
return session->user->name;
}
+static const char *session_account_type(struct ksmbd_session *session)
+{
+ if (user_guest(session->user))
+ return "guest";
+ if (ksmbd_anonymous_user(session->user))
+ return "anonymous";
+ return "user";
+}
+
+static unsigned int session_open_file_count(struct ksmbd_session *session)
+{
+ struct ksmbd_file *fp;
+ unsigned int count = 0;
+ unsigned int id;
+
+ read_lock(&session->file_table.lock);
+ idr_for_each_entry(session->file_table.idr, fp, id)
+ count++;
+ read_unlock(&session->file_table.lock);
+ return count;
+}
+
static int show_proc_session(struct seq_file *m, void *v)
{
struct ksmbd_session *sess;
@@ -90,9 +112,16 @@ static int show_proc_session(struct seq_file *m, void *v)
sess = (struct ksmbd_session *)m->private;
ksmbd_user_session_get(sess);
- seq_printf(m, "%-20s\t%s\n", "user", session_user_name(sess));
- seq_printf(m, "%-20s\t%llu\n", "id", sess->id);
- seq_printf(m, "%-20s\t%s\n", "state", session_state_string(sess));
+ seq_printf(m, "user:\t%s\n", session_user_name(sess));
+ seq_printf(m, "account_type:\t%s\n",
+ session_account_type(sess));
+ seq_printf(m, "id:\t%llu\n", sess->id);
+ seq_printf(m, "state:\t%s\n", session_state_string(sess));
+ seq_printf(m, "dialect:\t0x%04x\n", sess->dialect);
+ seq_printf(m, "last_active_seconds:\t%lu\n",
+ jiffies_to_msecs(jiffies - sess->last_active) / MSEC_PER_SEC);
+ seq_printf(m, "open_files:\t%u\n",
+ session_open_file_count(sess));
i = 0;
down_read(&sess->chann_lock);
@@ -101,21 +130,23 @@ static int show_proc_session(struct seq_file *m, void *v)
#if IS_ENABLED(CONFIG_IPV6)
if (chan->conn->inet_addr)
- seq_printf(m, "%-20s\t%pI4\n", "client",
+ seq_printf(m, "client:\t%pI4\n",
&chan->conn->inet_addr);
else
- seq_printf(m, "%-20s\t%pI6c\n", "client",
+ seq_printf(m, "client:\t%pI6c\n",
&chan->conn->inet6_addr);
#else
- seq_printf(m, "%-20s\t%pI4\n", "client",
+ seq_printf(m, "client:\t%pI4\n",
&chan->conn->inet_addr);
#endif
- seq_printf(m, "%-20s\t", "capabilities");
+ seq_puts(m, "capabilities:\t");
ksmbd_proc_show_flag_names(m,
ksmbd_sess_cap_const_names,
ARRAY_SIZE(ksmbd_sess_cap_const_names),
chan->conn->vals->req_capabilities);
seq_putc(m, '\n');
+ seq_printf(m, "posix_extensions:\t%s\n",
+ chan->conn->posix_ext_supported ? "yes" : "no");
if (sess->sign) {
unsigned int algorithm =
@@ -125,9 +156,9 @@ static int show_proc_session(struct seq_file *m, void *v)
ARRAY_SIZE(ksmbd_signing_const_names),
algorithm);
if (name)
- seq_printf(m, "%-20s\t%s\n", "signing", name);
+ seq_printf(m, "signing:\t%s\n", name);
else
- seq_printf(m, "%-20s\t0x%04x\n", "signing",
+ seq_printf(m, "signing:\t0x%04x\n",
algorithm);
}
if (sess->enc) {
@@ -137,30 +168,30 @@ static int show_proc_session(struct seq_file *m, void *v)
ARRAY_SIZE(ksmbd_cipher_const_names),
cipher);
if (name)
- seq_printf(m, "%-20s\t%s\n", "encryption", name);
+ seq_printf(m, "encryption:\t%s\n", name);
else
- seq_printf(m, "%-20s\t0x%04x\n", "encryption",
+ seq_printf(m, "encryption:\t0x%04x\n",
cipher);
}
i++;
}
up_read(&sess->chann_lock);
- seq_printf(m, "%-20s\t%d\n", "channels", i);
+ seq_printf(m, "channels:\t%d\n", i);
i = 0;
down_read(&sess->tree_conns_lock);
xa_for_each(&sess->tree_conns, id, tree_conn) {
share_conf = tree_conn->share_conf;
- seq_printf(m, "%-20s\t%s\t%8d", "share",
- share_conf->name, tree_conn->id);
- if (test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_PIPE))
- seq_printf(m, " %s ", "pipe");
- else
- seq_printf(m, " %s ", "disk");
- seq_putc(m, '\n');
+ seq_printf(m, "share:\t%s\n", share_conf->name);
+ seq_printf(m, "tree_id:\t%d\n", tree_conn->id);
+ seq_printf(m, "share_type:\t%s\n",
+ test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_PIPE) ?
+ "pipe" : "disk");
+ i++;
}
up_read(&sess->tree_conns_lock);
+ seq_printf(m, "tree_connects:\t%d\n", i);
ksmbd_user_session_put(sess);
return 0;
@@ -189,9 +220,6 @@ static int show_proc_sessions(struct seq_file *m, void *v)
int i;
unsigned long id;
- seq_printf(m, "#%-40s %-15s %-10s %-10s\n",
- "<client>", "<user>", "<sess_id>", "<state>");
-
down_read(&sessions_table_lock);
hash_for_each(sessions_table, i, session, hlist) {
down_read(&session->chann_lock);
@@ -201,13 +229,13 @@ static int show_proc_sessions(struct seq_file *m, void *v)
#if IS_ENABLED(CONFIG_IPV6)
if (!chan->conn->inet_addr)
- seq_printf(m, " %-40pI6c", &chan->conn->inet6_addr);
+ seq_printf(m, "client:\t%pI6c\n", &chan->conn->inet6_addr);
else
#endif
- seq_printf(m, " %-40pI4", &chan->conn->inet_addr);
- seq_printf(m, " %-15s %-10llu %-10s\n",
- session_user_name(session),
- session->id,
+ seq_printf(m, "client:\t%pI4\n", &chan->conn->inet_addr);
+ seq_printf(m, "user:\t%s\n", session_user_name(session));
+ seq_printf(m, "id:\t%llu\n", session->id);
+ seq_printf(m, "state:\t%s\n\n",
session_state_string(session));
ksmbd_user_session_put(session);
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index c867cf32ae9c..5c1929a81234 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -70,24 +70,63 @@ static const struct ksmbd_const_name ksmbd_oplock_const_names[] = {
{SMB2_OPLOCK_LEVEL_BATCH, "OPLOCK_BATCH"},
};
+static const struct ksmbd_const_name ksmbd_file_state_names[] = {
+ {FP_NEW, "new"},
+ {FP_INITED, "open"},
+ {FP_CLOSED, "closed"},
+};
+
+#define KSMBD_PROC_FILE_DURABLE BIT(0)
+#define KSMBD_PROC_FILE_PERSISTENT BIT(1)
+#define KSMBD_PROC_FILE_RESILIENT BIT(2)
+#define KSMBD_PROC_FILE_DELETE_ON_CLOSE BIT(3)
+#define KSMBD_PROC_FILE_STREAM BIT(4)
+#define KSMBD_PROC_FILE_POSIX BIT(5)
+#define KSMBD_PROC_FILE_ATTRIB_ONLY BIT(6)
+
+static const struct ksmbd_const_name ksmbd_file_flag_names[] = {
+ {KSMBD_PROC_FILE_DURABLE, "durable"},
+ {KSMBD_PROC_FILE_PERSISTENT, "persistent"},
+ {KSMBD_PROC_FILE_RESILIENT, "resilient"},
+ {KSMBD_PROC_FILE_DELETE_ON_CLOSE, "delete-on-close"},
+ {KSMBD_PROC_FILE_STREAM, "stream"},
+ {KSMBD_PROC_FILE_POSIX, "posix"},
+ {KSMBD_PROC_FILE_ATTRIB_ONLY, "attrib-only"},
+};
+
+static unsigned int ksmbd_proc_file_flags(struct ksmbd_file *fp)
+{
+ unsigned int flags = 0;
+
+ if (fp->is_durable)
+ flags |= KSMBD_PROC_FILE_DURABLE;
+ if (fp->is_persistent)
+ flags |= KSMBD_PROC_FILE_PERSISTENT;
+ if (fp->is_resilient)
+ flags |= KSMBD_PROC_FILE_RESILIENT;
+ if (fp->coption & FILE_DELETE_ON_CLOSE_LE)
+ flags |= KSMBD_PROC_FILE_DELETE_ON_CLOSE;
+ if (fp->stream.name)
+ flags |= KSMBD_PROC_FILE_STREAM;
+ if (fp->is_posix_ctxt)
+ flags |= KSMBD_PROC_FILE_POSIX;
+ if (fp->attrib_only)
+ flags |= KSMBD_PROC_FILE_ATTRIB_ONLY;
+ return flags;
+}
+
static int proc_show_files(struct seq_file *m, void *v)
{
struct ksmbd_file *fp = NULL;
unsigned int id;
struct oplock_info *opinfo;
- seq_printf(m, "#%-10s %-18s %-18s %-10s %-16s %-10s %-10s %s\n",
- "<tree id>", "<pid>", "<vid>", "<refcnt>",
- "<oplock>", "<daccess>", "<saccess>",
- "<name>");
-
read_lock(&global_ft.lock);
idr_for_each_entry(global_ft.idr, fp, id) {
- seq_printf(m, " %#-10x %#-18llx %#-18llx %#-10x",
- fp->tcon ? fp->tcon->id : 0,
- fp->persistent_id,
- fp->volatile_id,
- atomic_read(&fp->refcount));
+ seq_printf(m, "tree_id:\t0x%x\n", fp->tcon ? fp->tcon->id : 0);
+ seq_printf(m, "persistent_id:\t0x%llx\n", fp->persistent_id);
+ seq_printf(m, "volatile_id:\t0x%llx\n", fp->volatile_id);
+ seq_printf(m, "refcount:\t%d\n", atomic_read(&fp->refcount));
rcu_read_lock();
opinfo = rcu_dereference(fp->f_opinfo);
@@ -109,17 +148,30 @@ static int proc_show_files(struct seq_file *m, void *v)
rcu_read_unlock();
name = ksmbd_proc_const_name(const_names, count, level);
if (name)
- seq_printf(m, " %-16s", name);
+ seq_printf(m, "oplock:\t%s\n", name);
else
- seq_printf(m, " 0x%-14x", level);
+ seq_printf(m, "oplock:\t0x%x\n", level);
} else {
rcu_read_unlock();
- seq_printf(m, " %-16s", " ");
+ seq_puts(m, "oplock:\tnone\n");
}
- seq_printf(m, " %#010x %#010x %s\n",
- le32_to_cpu(fp->daccess),
- le32_to_cpu(fp->saccess),
+ seq_printf(m, "state:\t%s\n",
+ ksmbd_proc_const_name(ksmbd_file_state_names,
+ ARRAY_SIZE(ksmbd_file_state_names),
+ fp->f_state));
+ seq_printf(m, "durable_timeout:\t%u\n", fp->durable_timeout);
+ seq_printf(m, "create_options:\t0x%08x\n",
+ le32_to_cpu(fp->coption));
+ seq_printf(m, "desired_access:\t0x%08x\n",
+ le32_to_cpu(fp->daccess));
+ seq_printf(m, "share_access:\t0x%08x\n",
+ le32_to_cpu(fp->saccess));
+ seq_puts(m, "flags:\t");
+ ksmbd_proc_show_flag_names(m, ksmbd_file_flag_names,
+ ARRAY_SIZE(ksmbd_file_flag_names),
+ ksmbd_proc_file_flags(fp));
+ seq_printf(m, "\nname:\t%s\n\n",
fp->filp->f_path.dentry->d_name.name);
}
read_unlock(&global_ft.lock);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 6/7] ksmbd: add procfs monitoring for active shares
2026-07-16 9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
` (3 preceding siblings ...)
2026-07-16 9:57 ` [PATCH 5/7] ksmbd: report session and open file details " Namjae Jeon
@ 2026-07-16 9:57 ` Namjae Jeon
2026-07-16 9:57 ` [PATCH 7/7] ksmbd: extend procfs server statistics Namjae Jeon
5 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-16 9:57 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
There is no kernel-side view of the share configurations currently cached
by active tree connections.
Add a shares proc entry that reports each active share name, type,
tree-connection count, create masks, and descriptive configuration flags.
Maintain a per-share tree-connection counter with the existing global
counter so the value can be read without walking every session.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/mgmt/share_config.c | 57 +++++++++++++++++++++++++++++++
fs/smb/server/mgmt/share_config.h | 25 ++++++++++++++
fs/smb/server/mgmt/tree_connect.c | 2 ++
fs/smb/server/server.c | 1 +
4 files changed, 85 insertions(+)
diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c
index 6f97f8d39657..1cb58bec0903 100644
--- a/fs/smb/server/mgmt/share_config.c
+++ b/fs/smb/server/mgmt/share_config.c
@@ -28,6 +28,62 @@ struct ksmbd_veto_pattern {
struct list_head list;
};
+#ifdef CONFIG_PROC_FS
+static const struct ksmbd_const_name ksmbd_share_flag_names[] = {
+ {KSMBD_SHARE_FLAG_AVAILABLE, "available"},
+ {KSMBD_SHARE_FLAG_BROWSEABLE, "browseable"},
+ {KSMBD_SHARE_FLAG_WRITEABLE, "writeable"},
+ {KSMBD_SHARE_FLAG_READONLY, "read-only"},
+ {KSMBD_SHARE_FLAG_GUEST_OK, "guest-ok"},
+ {KSMBD_SHARE_FLAG_GUEST_ONLY, "guest-only"},
+ {KSMBD_SHARE_FLAG_STORE_DOS_ATTRS, "store-dos-attrs"},
+ {KSMBD_SHARE_FLAG_OPLOCKS, "oplocks"},
+ {KSMBD_SHARE_FLAG_PIPE, "pipe"},
+ {KSMBD_SHARE_FLAG_HIDE_DOT_FILES, "hide-dot-files"},
+ {KSMBD_SHARE_FLAG_INHERIT_OWNER, "inherit-owner"},
+ {KSMBD_SHARE_FLAG_STREAMS, "streams"},
+ {KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS, "follow-symlinks"},
+ {KSMBD_SHARE_FLAG_ACL_XATTR, "acl-xattr"},
+ {KSMBD_SHARE_FLAG_UPDATE, "update"},
+ {KSMBD_SHARE_FLAG_CROSSMNT, "crossmnt"},
+ {KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY, "continuous-availability"},
+};
+
+static int proc_show_shares(struct seq_file *m, void *v)
+{
+ struct ksmbd_share_config *share;
+ int i;
+
+ down_read(&shares_table_lock);
+ hash_for_each(shares_table, i, share, hlist) {
+ seq_printf(m, "name:\t%s\n", share->name);
+ seq_printf(m, "type:\t%s\n",
+ test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE) ?
+ "pipe" : "disk");
+ seq_printf(m, "tree_connects:\t%d\n",
+ atomic_read(&share->tree_connections));
+ seq_printf(m, "file_mask:\t0%07o\n", share->create_mask);
+ seq_printf(m, "directory_mask:\t0%07o\n", share->directory_mask);
+ seq_puts(m, "flags:\t");
+ ksmbd_proc_show_flag_names(m, ksmbd_share_flag_names,
+ ARRAY_SIZE(ksmbd_share_flag_names),
+ share->flags);
+ seq_puts(m, "\n\n");
+ }
+ up_read(&shares_table_lock);
+ return 0;
+}
+
+int create_proc_shares(void)
+{
+ if (!ksmbd_proc_create("shares", proc_show_shares, NULL))
+ return -ENOMEM;
+ return 0;
+}
+#else
+int create_proc_shares(void) { return 0; }
+#endif
+
static unsigned int share_name_hash(const char *name)
{
return jhash(name, strlen(name), 0);
@@ -156,6 +212,7 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
share->flags = resp->flags;
atomic_set(&share->refcount, 1);
+ ksmbd_share_tree_conn_init(share);
INIT_LIST_HEAD(&share->veto_list);
share->name = kstrdup(name, KSMBD_DEFAULT_GFP);
diff --git a/fs/smb/server/mgmt/share_config.h b/fs/smb/server/mgmt/share_config.h
index d4ac2dd4de20..d157545fe7d1 100644
--- a/fs/smb/server/mgmt/share_config.h
+++ b/fs/smb/server/mgmt/share_config.h
@@ -24,6 +24,9 @@ struct ksmbd_share_config {
struct path vfs_path;
atomic_t refcount;
+#ifdef CONFIG_PROC_FS
+ atomic_t tree_connections;
+#endif
struct hlist_node hlist;
unsigned short create_mask;
unsigned short directory_mask;
@@ -60,6 +63,27 @@ static inline int test_share_config_flag(struct ksmbd_share_config *share,
return share->flags & flag;
}
+#ifdef CONFIG_PROC_FS
+static inline void ksmbd_share_tree_conn_init(struct ksmbd_share_config *share)
+{
+ atomic_set(&share->tree_connections, 0);
+}
+
+static inline void ksmbd_share_tree_conn_inc(struct ksmbd_share_config *share)
+{
+ atomic_inc(&share->tree_connections);
+}
+
+static inline void ksmbd_share_tree_conn_dec(struct ksmbd_share_config *share)
+{
+ atomic_dec(&share->tree_connections);
+}
+#else
+static inline void ksmbd_share_tree_conn_init(struct ksmbd_share_config *share) {}
+static inline void ksmbd_share_tree_conn_inc(struct ksmbd_share_config *share) {}
+static inline void ksmbd_share_tree_conn_dec(struct ksmbd_share_config *share) {}
+#endif
+
void ksmbd_share_config_del(struct ksmbd_share_config *share);
void __ksmbd_share_config_put(struct ksmbd_share_config *share);
@@ -74,4 +98,5 @@ struct ksmbd_share_config *ksmbd_share_config_get(struct ksmbd_work *work,
const char *name);
bool ksmbd_share_veto_filename(struct ksmbd_share_config *share,
const char *filename);
+int create_proc_shares(void);
#endif /* __SHARE_CONFIG_MANAGEMENT_H__ */
diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c
index 58e5b8592da4..5f63e236267a 100644
--- a/fs/smb/server/mgmt/tree_connect.c
+++ b/fs/smb/server/mgmt/tree_connect.c
@@ -88,6 +88,7 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
goto out_error;
}
ksmbd_counter_inc(KSMBD_COUNTER_TREE_CONNS);
+ ksmbd_share_tree_conn_inc(sc);
kvfree(resp);
return status;
@@ -116,6 +117,7 @@ static int __ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id);
ksmbd_release_tree_conn_id(sess, tree_conn->id);
ksmbd_counter_dec(KSMBD_COUNTER_TREE_CONNS);
+ ksmbd_share_tree_conn_dec(tree_conn->share_conf);
if (atomic_dec_and_test(&tree_conn->refcount)) {
ksmbd_share_config_put(tree_conn->share_conf);
kfree(tree_conn);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index b0e9689fd3c8..7a2730e71fd2 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -604,6 +604,7 @@ static int __init ksmbd_server_init(void)
ksmbd_proc_init();
create_proc_sessions();
+ create_proc_shares();
ksmbd_server_tcp_callbacks_init();
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 7/7] ksmbd: extend procfs server statistics
2026-07-16 9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
` (4 preceding siblings ...)
2026-07-16 9:57 ` [PATCH 6/7] ksmbd: add procfs monitoring for active shares Namjae Jeon
@ 2026-07-16 9:57 ` Namjae Jeon
5 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-16 9:57 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
The server proc entry does not expose configured limits or enough outcome
data to distinguish protocol errors from transport stalls.
Report the server state, listener and signing configuration, connection
limits, timeout values, current client and open-file totals, IPC activity,
and durable scavenger state. Classify processed SMB2 response statuses by
NTSTATUS severity and provide counters for common error groups while
retaining the per-command counters.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/proc.c | 121 +++++++++++++++++++++++++++++++++----
fs/smb/server/server.c | 7 ++-
fs/smb/server/smb_common.h | 2 +-
fs/smb/server/stats.h | 53 +++++++++++++++-
fs/smb/server/vfs_cache.c | 10 +++
fs/smb/server/vfs_cache.h | 1 +
6 files changed, 176 insertions(+), 18 deletions(-)
diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c
index b41490142480..1bf4e00dee34 100644
--- a/fs/smb/server/proc.c
+++ b/fs/smb/server/proc.c
@@ -11,10 +11,12 @@
#include <linux/seq_file.h>
#include "misc.h"
+#include "connection.h"
#include "server.h"
#include "stats.h"
#include "smb_common.h"
#include "smb2pdu.h"
+#include "vfs_cache.h"
static struct proc_dir_entry *ksmbd_proc_fs;
struct ksmbd_counters ksmbd_counters;
@@ -90,34 +92,127 @@ static const struct ksmbd_const_smb2_process_req smb2_process_req[KSMBD_COUNTER_
{le16_to_cpu(SMB2_OPLOCK_BREAK), "SMB2_OPLOCK_BREAK"},
};
+static const char *ksmbd_server_state_string(void)
+{
+ switch (READ_ONCE(server_conf.state)) {
+ case SERVER_STATE_STARTING_UP:
+ return "starting";
+ case SERVER_STATE_RUNNING:
+ return "running";
+ case SERVER_STATE_RESETTING:
+ return "resetting";
+ case SERVER_STATE_SHUTTING_DOWN:
+ return "shutdown";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *ksmbd_signing_mode_string(void)
+{
+ switch (server_conf.signing) {
+ case KSMBD_CONFIG_OPT_DISABLED:
+ return "disabled";
+ case KSMBD_CONFIG_OPT_MANDATORY:
+ return "mandatory";
+ case KSMBD_CONFIG_OPT_AUTO:
+ return "auto";
+ default:
+ return "unknown";
+ }
+}
+
+static void proc_show_runtime_totals(struct seq_file *m)
+{
+ struct ksmbd_conn *conn;
+ unsigned int clients = 0;
+ unsigned int open_files = 0;
+ int i;
+
+ down_read(&conn_list_lock);
+ hash_for_each(conn_list, i, conn, hlist) {
+ clients++;
+ open_files += atomic_read(&conn->stats.open_files_count);
+ }
+ up_read(&conn_list_lock);
+
+ seq_printf(m, "clients:\t%u\n", clients);
+ seq_printf(m, "open_files:\t%u\n", open_files);
+}
+
static int proc_show_ksmbd_stats(struct seq_file *m, void *v)
{
int i;
seq_puts(m, "Server\n");
- seq_printf(m, "name: %s\n", ksmbd_server_string());
- seq_printf(m, "netbios: %s\n", ksmbd_netbios_name());
- seq_printf(m, "work group: %s\n", ksmbd_work_group());
- seq_printf(m, "min protocol: %s\n", ksmbd_get_protocol_string(server_conf.min_protocol));
- seq_printf(m, "max protocol: %s\n", ksmbd_get_protocol_string(server_conf.max_protocol));
- seq_printf(m, "flags: 0x%08x\n", server_conf.flags);
- seq_printf(m, "share_fake_fscaps: 0x%08x\n",
+ seq_printf(m, "state:\t%s\n", ksmbd_server_state_string());
+ seq_printf(m, "name:\t%s\n", ksmbd_server_string());
+ seq_printf(m, "netbios:\t%s\n", ksmbd_netbios_name());
+ seq_printf(m, "work_group:\t%s\n", ksmbd_work_group());
+ seq_printf(m, "min_protocol:\t%s\n", ksmbd_get_protocol_string(server_conf.min_protocol));
+ seq_printf(m, "max_protocol:\t%s\n", ksmbd_get_protocol_string(server_conf.max_protocol));
+ seq_printf(m, "flags:\t0x%08x\n", server_conf.flags);
+ seq_printf(m, "tcp_port:\t%u\n", server_conf.tcp_port);
+ seq_printf(m, "signing:\t%s\n", ksmbd_signing_mode_string());
+ seq_printf(m, "signing_enforced:\t%s\n",
+ server_conf.enforced_signing ? "yes" : "no");
+ seq_printf(m, "bind_interfaces_only:\t%s\n",
+ server_conf.bind_interfaces_only ? "yes" : "no");
+ seq_printf(m, "max_connections:\t%u\n", server_conf.max_connections);
+ seq_printf(m, "max_connections_per_ip:\t%u\n",
+ server_conf.max_ip_connections);
+ seq_printf(m, "max_inflight_requests:\t%u\n",
+ server_conf.max_inflight_req);
+ seq_printf(m, "deadtime_seconds:\t%lu\n", server_conf.deadtime / HZ);
+ seq_printf(m, "ipc_timeout_seconds:\t%u\n", server_conf.ipc_timeout / HZ);
+ if (server_conf.ipc_last_active)
+ seq_printf(m, "ipc_last_active_seconds:\t%lu\n",
+ jiffies_to_msecs(jiffies - server_conf.ipc_last_active) /
+ MSEC_PER_SEC);
+ else
+ seq_puts(m, "ipc_last_active_seconds:\tnever\n");
+ seq_printf(m, "durable_scavenger:\t%s\n",
+ ksmbd_durable_scavenger_active() ? "running" : "stopped");
+ seq_printf(m, "share_fake_fscaps:\t0x%08x\n",
server_conf.share_fake_fscaps);
- seq_printf(m, "sessions: %lld\n",
+ proc_show_runtime_totals(m);
+ seq_printf(m, "sessions:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS));
- seq_printf(m, "tree connects: %lld\n",
+ seq_printf(m, "tree_connects:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS));
- seq_printf(m, "requests: %lld\n",
+ seq_printf(m, "requests:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_REQUESTS));
- seq_printf(m, "read bytes: %lld\n",
+ seq_printf(m, "read_bytes:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_READ_BYTES));
- seq_printf(m, "written bytes: %lld\n",
+ seq_printf(m, "written_bytes:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_WRITE_BYTES));
seq_puts(m, "\nSMB2\n");
for (i = 0; i < KSMBD_COUNTER_MAX_REQS; i++)
- seq_printf(m, "%-20s:\t%lld\n", smb2_process_req[i].name,
+ seq_printf(m, "%s:\t%lld\n", smb2_process_req[i].name,
ksmbd_counter_sum(KSMBD_COUNTER_FIRST_REQ + i));
+
+ seq_puts(m, "\nSMB2 status\n");
+ seq_printf(m, "success:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_SUCCESS));
+ seq_printf(m, "informational:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_INFORMATIONAL));
+ seq_printf(m, "warning:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_WARNING));
+ seq_printf(m, "error:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_ERROR));
+ seq_printf(m, "access_denied:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_ACCESS_DENIED));
+ seq_printf(m, "not_found:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_NOT_FOUND));
+ seq_printf(m, "invalid_parameter:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_INVALID_PARAMETER));
+ seq_printf(m, "sharing_violation:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_SHARING_VIOLATION));
+ seq_printf(m, "not_supported:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_NOT_SUPPORTED));
+ seq_printf(m, "other:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_OTHER));
return 0;
}
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 7a2730e71fd2..348dd03981f1 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -148,8 +148,11 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,
}
ret = cmds->proc(work);
- if (conn->ops->inc_reqs)
- conn->ops->inc_reqs(command);
+ if (conn->ops->inc_reqs) {
+ struct smb2_hdr *rsp = ksmbd_resp_buf_curr(work);
+
+ conn->ops->inc_reqs(command, rsp->Status);
+ }
if (ret < 0)
ksmbd_debug(CONN, "Failed to process %u [%d]\n", command, ret);
diff --git a/fs/smb/server/smb_common.h b/fs/smb/server/smb_common.h
index b090b56743c4..7b9c5cfcb63b 100644
--- a/fs/smb/server/smb_common.h
+++ b/fs/smb/server/smb_common.h
@@ -135,7 +135,7 @@ struct file_id_both_directory_info {
struct smb_version_ops {
u16 (*get_cmd_val)(struct ksmbd_work *swork);
- void (*inc_reqs)(unsigned int cmd);
+ void (*inc_reqs)(unsigned int cmd, __le32 status);
int (*init_rsp_hdr)(struct ksmbd_work *swork);
void (*set_rsp_status)(struct ksmbd_work *swork, __le32 err);
int (*allocate_rsp_buf)(struct ksmbd_work *work);
diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h
index 08ee66f91eaa..bc864efa0d46 100644
--- a/fs/smb/server/stats.h
+++ b/fs/smb/server/stats.h
@@ -9,12 +9,24 @@
#ifndef __KSMBD_STATS_H__
#define __KSMBD_STATS_H__
+#include "../common/smb2status.h"
+
#define KSMBD_COUNTER_MAX_REQS 19
enum {
KSMBD_COUNTER_SESSIONS = 0,
KSMBD_COUNTER_TREE_CONNS,
KSMBD_COUNTER_REQUESTS,
+ KSMBD_COUNTER_STATUS_SUCCESS,
+ KSMBD_COUNTER_STATUS_INFORMATIONAL,
+ KSMBD_COUNTER_STATUS_WARNING,
+ KSMBD_COUNTER_STATUS_ERROR,
+ KSMBD_COUNTER_ERROR_ACCESS_DENIED,
+ KSMBD_COUNTER_ERROR_NOT_FOUND,
+ KSMBD_COUNTER_ERROR_INVALID_PARAMETER,
+ KSMBD_COUNTER_ERROR_SHARING_VIOLATION,
+ KSMBD_COUNTER_ERROR_NOT_SUPPORTED,
+ KSMBD_COUNTER_ERROR_OTHER,
KSMBD_COUNTER_READ_BYTES,
KSMBD_COUNTER_WRITE_BYTES,
KSMBD_COUNTER_FIRST_REQ,
@@ -50,8 +62,45 @@ static inline void ksmbd_counter_sub(int type, s64 value)
percpu_counter_sub(&ksmbd_counters.counters[type], value);
}
-static inline void ksmbd_counter_inc_reqs(unsigned int cmd)
+static inline void ksmbd_counter_inc_reqs(unsigned int cmd, __le32 status)
{
+ unsigned int severity = le32_to_cpu(status) >> 30;
+ int type;
+
+ switch (severity) {
+ case 0:
+ type = KSMBD_COUNTER_STATUS_SUCCESS;
+ break;
+ case 1:
+ type = KSMBD_COUNTER_STATUS_INFORMATIONAL;
+ break;
+ case 2:
+ type = KSMBD_COUNTER_STATUS_WARNING;
+ break;
+ default:
+ type = KSMBD_COUNTER_STATUS_ERROR;
+ break;
+ }
+ percpu_counter_inc(&ksmbd_counters.counters[type]);
+
+ if (severity == 3) {
+ if (status == STATUS_ACCESS_DENIED)
+ type = KSMBD_COUNTER_ERROR_ACCESS_DENIED;
+ else if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
+ status == STATUS_NO_SUCH_FILE)
+ type = KSMBD_COUNTER_ERROR_NOT_FOUND;
+ else if (status == STATUS_INVALID_PARAMETER)
+ type = KSMBD_COUNTER_ERROR_INVALID_PARAMETER;
+ else if (status == STATUS_SHARING_VIOLATION)
+ type = KSMBD_COUNTER_ERROR_SHARING_VIOLATION;
+ else if (status == STATUS_NOT_SUPPORTED ||
+ status == STATUS_NOT_IMPLEMENTED)
+ type = KSMBD_COUNTER_ERROR_NOT_SUPPORTED;
+ else
+ type = KSMBD_COUNTER_ERROR_OTHER;
+ percpu_counter_inc(&ksmbd_counters.counters[type]);
+ }
+
if (cmd < KSMBD_COUNTER_MAX_REQS) {
percpu_counter_inc(&ksmbd_counters.counters[KSMBD_COUNTER_REQUESTS]);
percpu_counter_inc(&ksmbd_counters.counters[KSMBD_COUNTER_FIRST_REQ + cmd]);
@@ -68,7 +117,7 @@ static inline void ksmbd_counter_inc(int type) {}
static inline void ksmbd_counter_dec(int type) {}
static inline void ksmbd_counter_add(int type, s64 value) {}
static inline void ksmbd_counter_sub(int type, s64 value) {}
-static inline void ksmbd_counter_inc_reqs(unsigned int cmd) {}
+static inline void ksmbd_counter_inc_reqs(unsigned int cmd, __le32 status) {}
static inline s64 ksmbd_counter_sum(int type) { return 0; }
#endif
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 5c1929a81234..68bf7b0512da 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -191,6 +191,16 @@ static bool durable_scavenger_running;
static DEFINE_MUTEX(durable_scavenger_lock);
static wait_queue_head_t dh_wq;
+bool ksmbd_durable_scavenger_active(void)
+{
+ bool active;
+
+ mutex_lock(&durable_scavenger_lock);
+ active = durable_scavenger_running;
+ mutex_unlock(&durable_scavenger_lock);
+ return active;
+}
+
void ksmbd_set_fd_limit(unsigned long limit)
{
limit = min(limit, get_max_files());
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 5aff9bb556ec..5fac4b0b419d 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -216,6 +216,7 @@ unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);
struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp);
void ksmbd_launch_ksmbd_durable_scavenger(void);
void ksmbd_stop_durable_scavenger(void);
+bool ksmbd_durable_scavenger_active(void);
void ksmbd_close_tree_conn_fds(struct ksmbd_work *work);
void ksmbd_close_session_fds(struct ksmbd_work *work);
int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread