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(¬ify_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(¬ify->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, ¬ify);
+ 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, ¬ify_req);
+ err = ksmbd_notify_wait(work, notify, ¬ify_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
next prev 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