Linux CIFS filesystem development
 help / color / mirror / Atom feed
From: ChenXiaoSong <chenxiaosong@chenxiaosong.com>
To: linkinjeon@kernel.org, tom@talpey.com, senozhatsky@chromium.org,
	chenxiaosong@chenxiaosong.com
Cc: linux-cifs@vger.kernel.org, ChenXiaoSong <chenxiaosong@kylinos.cn>
Subject: [PATCH 04/12] smb/server: support non-recursive directory change watches
Date: Sat, 26 Sep 2026 09:05:10 +0000	[thread overview]
Message-ID: <20260926090518.78547-5-chenxiaosong@chenxiaosong.com> (raw)
In-Reply-To: <20260926090518.78547-1-chenxiaosong@chenxiaosong.com>

From: ChenXiaoSong <chenxiaosong@kylinos.cn>

Use fsnotify to watch a directory for changes. Add one watch for each
SMB2 CHANGE_NOTIFY request with SMB2_WATCH_TREE unset.

Example:

  1. client: smbinfo notify /mnt
  2. server: ksmbd.control --debug=notify
  3. server: touch /export/file
  4. server debug log:
     ksmbd: fid 4:4, notify event: mask=0x00000100 inode=2 name=file cookie=0
     ksmbd: fid 4:4, notify event: mask=0x08000004 inode=2 name=file cookie=0

Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
 fs/smb/server/Kconfig  |   1 +
 fs/smb/server/notify.c | 232 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 231 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/Kconfig b/fs/smb/server/Kconfig
index b7665e0e4942..1f91926151b2 100644
--- a/fs/smb/server/Kconfig
+++ b/fs/smb/server/Kconfig
@@ -19,6 +19,7 @@ config SMB_SERVER
 	select ASN1
 	select OID_REGISTRY
 	select CRC32
+	select FSNOTIFY
 	default n
 	help
 	  Choose Y here if you want to allow SMB3 compliant clients
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 502ec551c01e..b85f5fdb5066 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -10,6 +10,7 @@
  *
  */
 
+#include <linux/fsnotify_backend.h>
 #include "glob.h"
 #include "../common/smb2status.h"
 #include "connection.h"
@@ -20,10 +21,57 @@
 #include "vfs_cache.h"
 #include "mgmt/user_session.h"
 
+struct ksmbd_notify {
+	struct fsnotify_group *group;
+	struct fsnotify_mark *mark;
+	struct ksmbd_file *fp;
+	/* Protects filter, rename state and the queued events. */
+	spinlock_t lock;
+	u32 filter;
+	u32 mask;
+};
+
 struct ksmbd_notify_req {
 	wait_queue_head_t wait;
 };
 
+#define KSMBD_NOTIFY_NAME_EVENT_MASK	(FS_CREATE | FS_DELETE | \
+					 FS_MOVED_FROM | FS_MOVED_TO)
+
+static const struct {
+	u32 notify_mask;
+	u32 fsnotify_mask;
+} ksmbd_notify_mapping[] = {
+	{ FILE_NOTIFY_CHANGE_FILE_NAME,
+	  KSMBD_NOTIFY_NAME_EVENT_MASK },
+	{ FILE_NOTIFY_CHANGE_DIR_NAME,
+	  KSMBD_NOTIFY_NAME_EVENT_MASK },
+	{ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+	  FS_ATTRIB | FS_MOVED_FROM | FS_MOVED_TO | FS_MODIFY },
+	{ FILE_NOTIFY_CHANGE_SIZE, FS_MODIFY },
+	{ FILE_NOTIFY_CHANGE_LAST_WRITE, FS_ATTRIB },
+	{ FILE_NOTIFY_CHANGE_LAST_ACCESS, FS_ATTRIB },
+	{ FILE_NOTIFY_CHANGE_EA, FS_ATTRIB },
+	{ FILE_NOTIFY_CHANGE_SECURITY, FS_ATTRIB },
+};
+
+static u32 ksmbd_notify_map(u32 filter)
+{
+	size_t i;
+	u32 mask = 0;
+
+	for (i = 0; i < ARRAY_SIZE(ksmbd_notify_mapping); i++) {
+		if (ksmbd_notify_mapping[i].notify_mask & filter)
+			mask |= ksmbd_notify_mapping[i].fsnotify_mask;
+	}
+
+	ksmbd_debug(NOTIFY,
+		    "Mapped completion filter 0x%x to fsnotify mask 0x%x\n",
+		    filter, mask);
+
+	return mask;
+}
+
 /*
  * Cancel handler for a pending CHANGE_NOTIFY. Called either by
  * smb2_cancel() (conn->request_lock held, work->state already set to
@@ -43,6 +91,140 @@ static void smb2_notify_cancel(void **argv)
 	wake_up(&notify_req->wait);
 }
 
+static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
+					   u32 mask, struct inode *inode,
+					   struct inode *dir,
+					   const struct qstr *file_name,
+					   u32 cookie)
+{
+	struct inode *event_inode = dir ?: inode;
+	struct ksmbd_file *fp;
+
+	fp = notify->fp;
+
+	ksmbd_debug(NOTIFY,
+		    "fid %llu:%llu, notify event: mask=0x%08x inode=%llu name=%.*s cookie=%u\n",
+		    fp->persistent_id, fp->volatile_id, mask,
+		    event_inode ? (unsigned long long)event_inode->i_ino : 0,
+		    file_name ? file_name->len : 0,
+		    file_name ? (const char *)file_name->name : "", cookie);
+
+	return 0;
+}
+
+static int ksmbd_notify_handle_event(struct fsnotify_group *group, u32 mask,
+				     const void *data, int data_type,
+				     struct inode *dir,
+				     const struct qstr *file_name, u32 cookie,
+				     struct fsnotify_iter_info *iter_info)
+{
+	struct ksmbd_notify *notify = group->private;
+	struct inode *inode = fsnotify_data_inode(data, data_type);
+
+	return ksmbd_notify_handle_inode_event(notify, mask, inode, dir,
+					      file_name, cookie);
+}
+
+static void ksmbd_notify_free_mark(struct fsnotify_mark *mark)
+{
+	kfree(mark);
+}
+
+static const struct fsnotify_ops ksmbd_notify_fsnotify_ops = {
+	.handle_event = ksmbd_notify_handle_event,
+	.free_mark = ksmbd_notify_free_mark,
+};
+
+static struct fsnotify_mark *
+ksmbd_notify_add_mark(struct ksmbd_notify *notify, u32 mask, void *obj,
+		      unsigned int obj_type)
+{
+	struct fsnotify_mark *mark;
+	int err;
+
+	mark = kzalloc_obj(*mark, KSMBD_DEFAULT_GFP);
+	if (!mark) {
+		pr_err("Failed to allocate fsnotify mark\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	fsnotify_init_mark(mark, notify->group);
+	mark->mask = mask | FS_EVENT_ON_CHILD;
+	err = fsnotify_add_mark(mark, obj, obj_type, 0);
+	if (err) {
+		pr_err("Failed to add fsnotify mark, type %u: %d\n",
+		       obj_type, err);
+		goto err_put_mark;
+	}
+
+	return mark;
+
+err_put_mark:
+	fsnotify_put_mark(mark);
+	return ERR_PTR(err);
+}
+
+static void ksmbd_notify_destroy_marks(struct ksmbd_notify *notify)
+{
+	if (notify->mark) {
+		fsnotify_destroy_mark(notify->mark, notify->group);
+		fsnotify_put_mark(notify->mark);
+	}
+	fsnotify_wait_marks_destroyed();
+	fsnotify_put_group(notify->group);
+}
+
+static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
+			    struct ksmbd_notify **notify_out)
+{
+	struct ksmbd_notify *notify;
+	struct fsnotify_mark *mark;
+	int err = 0;
+
+	notify = kzalloc_obj(*notify, KSMBD_DEFAULT_GFP);
+	if (!notify) {
+		pr_err("Failed to allocate notify watch\n");
+		err = -ENOMEM;
+		goto out;
+	}
+
+	notify->fp = fp;
+	spin_lock_init(&notify->lock);
+	notify->filter = filter;
+	notify->mask = mask;
+
+	notify->group = fsnotify_alloc_group(&ksmbd_notify_fsnotify_ops, 0);
+	if (IS_ERR(notify->group)) {
+		err = PTR_ERR(notify->group);
+		pr_err("Failed to allocate fsnotify group: %d\n", err);
+		kfree(notify);
+		goto out;
+	}
+	notify->group->private = notify;
+
+	mark = ksmbd_notify_add_mark(notify, mask, file_inode(fp->filp),
+				     FSNOTIFY_OBJ_TYPE_INODE);
+	if (IS_ERR(mark)) {
+		err = PTR_ERR(mark);
+		goto err_destroy_marks;
+	}
+	notify->mark = mark;
+
+	*notify_out = notify;
+	ksmbd_debug(NOTIFY,
+		    "Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x\n",
+		    (unsigned long long)file_inode(fp->filp)->i_ino, mark->mask,
+		    filter);
+	goto out;
+
+err_destroy_marks:
+	ksmbd_notify_destroy_marks(notify);
+	kfree(notify);
+
+out:
+	return err;
+}
+
 static struct ksmbd_file *
 ksmbd_notify_validate_req(struct ksmbd_work *work,
 			  struct smb2_change_notify_req *req,
@@ -82,6 +264,13 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
 		goto err_put_fp;
 	}
 
+	if (le16_to_cpu(req->Flags) & ~SMB2_WATCH_TREE) {
+		pr_err("Invalid notify flags 0x%x\n", le16_to_cpu(req->Flags));
+		rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+		err = -EINVAL;
+		goto err_put_fp;
+	}
+
 	if (!S_ISDIR(file_inode(fp->filp)->i_mode)) {
 		pr_err("Notify file id is not a directory, fid %llu:%llu\n",
 		       fp->persistent_id, fp->volatile_id);
@@ -105,10 +294,37 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
 	return ERR_PTR(err);
 }
 
+static struct ksmbd_notify *
+ksmbd_notify_setup_watch(struct ksmbd_file *fp,
+			 struct smb2_change_notify_req *req,
+			 struct smb2_change_notify_rsp *rsp)
+{
+	struct ksmbd_notify *notify;
+	u32 filter, mask;
+	int err;
+
+	filter = le32_to_cpu(req->CompletionFilter);
+	mask = ksmbd_notify_map(filter);
+	if (!mask)
+		ksmbd_debug(NOTIFY,
+			    "No mapped completion filter bits; request will remain pending\n");
+
+	err = ksmbd_notify_add(fp, mask, filter, &notify);
+	if (err) {
+		pr_err("Failed to add notify watch, fid %llu:%llu: %d\n",
+		       fp->persistent_id, fp->volatile_id, err);
+		rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+		return ERR_PTR(err);
+	}
+
+	return notify;
+}
+
 static int ksmbd_notify_wait(struct ksmbd_work *work,
-			     struct ksmbd_file *fp,
+			     struct ksmbd_notify *notify,
 			     struct ksmbd_notify_req *notify_req)
 {
+	struct ksmbd_file *fp = notify->fp;
 	int err;
 
 	/*
@@ -165,6 +381,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
 			struct smb2_change_notify_rsp *rsp)
 {
 	struct ksmbd_notify_req notify_req = {};
+	struct ksmbd_notify *notify = NULL;
 	struct ksmbd_file *fp = NULL;
 	void **argv = NULL;
 	bool async_work = false;
@@ -177,6 +394,13 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
 		goto out;
 	}
 
+	notify = ksmbd_notify_setup_watch(fp, req, rsp);
+	if (IS_ERR(notify)) {
+		err = PTR_ERR(notify);
+		notify = NULL;
+		goto out;
+	}
+
 	argv = kmalloc_obj(*argv, KSMBD_DEFAULT_GFP);
 	if (!argv) {
 		pr_err("Failed to allocate notify cancel arguments\n");
@@ -195,7 +419,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
 	}
 	async_work = true;
 
-	err = ksmbd_notify_wait(work, fp, &notify_req);
+	err = ksmbd_notify_wait(work, notify, &notify_req);
 	if (err == -ENOENT) {
 		rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
 		goto out;
@@ -220,6 +444,10 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
 		release_async_work(work);
 	else
 		kfree(argv);
+	if (notify) {
+		ksmbd_notify_destroy_marks(notify);
+		kfree(notify);
+	}
 	if (fp)
 		ksmbd_fd_put(work, fp);
 	return err;
-- 
2.55.0


  parent reply	other threads:[~2026-09-26  9:06 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-26  9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
2026-09-26  9:05 ` [PATCH 01/12] smb/server: move change notify handling into notify.c ChenXiaoSong
2026-09-26  9:05 ` [PATCH 02/12] smb/server: add more validation for change notify requests ChenXiaoSong
2026-09-26  9:05 ` [PATCH 03/12] smb/server: add debug type for change notify ChenXiaoSong
2026-09-26  9:05 ` ChenXiaoSong [this message]
2026-09-27 10:51   ` [PATCH 04/12] smb/server: support non-recursive directory change watches Namjae Jeon
2026-09-28  0:38     ` ChenXiaoSong
2026-09-26  9:05 ` [PATCH 05/12] smb/server: support recursive " ChenXiaoSong
2026-09-26  9:05 ` [PATCH 06/12] smb/server: keep notify watches on file handles ChenXiaoSong
2026-09-26  9:05 ` [PATCH 07/12] smb/server: save simple notify events ChenXiaoSong
2026-09-27 10:31   ` Namjae Jeon
2026-09-28  1:27     ` ChenXiaoSong
2026-09-26  9:05 ` [PATCH 08/12] smb/server: save old names for rename " ChenXiaoSong
2026-09-26  9:05 ` [PATCH 09/12] smb/server: match " ChenXiaoSong
2026-09-26  9:05 ` [PATCH 10/12] smb/server: encode " ChenXiaoSong
2026-09-26  9:05 ` [PATCH 11/12] smb/server: send notify events to the client ChenXiaoSong
2026-09-26  9:05 ` [PATCH 12/12] smb/server: break directory leases before sending notify events ChenXiaoSong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260926090518.78547-5-chenxiaosong@chenxiaosong.com \
    --to=chenxiaosong@chenxiaosong.com \
    --cc=chenxiaosong@kylinos.cn \
    --cc=linkinjeon@kernel.org \
    --cc=linux-cifs@vger.kernel.org \
    --cc=senozhatsky@chromium.org \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox