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 05/12] smb/server: support recursive directory change watches
Date: Sat, 26 Sep 2026 09:05:11 +0000 [thread overview]
Message-ID: <20260926090518.78547-6-chenxiaosong@chenxiaosong.com> (raw)
In-Reply-To: <20260926090518.78547-1-chenxiaosong@chenxiaosong.com>
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Support SMB2 CHANGE_NOTIFY requests with SMB2_WATCH_TREE set.
Example:
1. client: smbinfo notify /mnt
2. server: ksmbd.control --debug=notify
3. server: mkdir /export/dir; touch /export/dir/file
4. server debug log:
ksmbd: fid 1:1, notify event: mask=0x00000100 inode=11 name=dir\file cookie=0
ksmbd: fid 1:1, notify event: mask=0x00000004 inode=11 name=dir\file cookie=0
Suggested-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 155 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 149 insertions(+), 6 deletions(-)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index b85f5fdb5066..b34f2f9b6d3f 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -11,6 +11,7 @@
*/
#include <linux/fsnotify_backend.h>
+#include <linux/dcache.h>
#include "glob.h"
#include "../common/smb2status.h"
#include "connection.h"
@@ -24,11 +25,13 @@
struct ksmbd_notify {
struct fsnotify_group *group;
struct fsnotify_mark *mark;
+ struct fsnotify_mark *tree_mark;
struct ksmbd_file *fp;
/* Protects filter, rename state and the queued events. */
spinlock_t lock;
u32 filter;
u32 mask;
+ bool watch_tree;
};
struct ksmbd_notify_req {
@@ -112,6 +115,100 @@ static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
return 0;
}
+static char *ksmbd_notify_tree_name(struct ksmbd_notify *notify,
+ const void *data, int data_type,
+ struct inode *dir,
+ const struct qstr *file_name,
+ struct qstr *tree_name)
+{
+ struct dentry *root = file_dentry(notify->fp->filp);
+ struct inode *inode = fsnotify_data_inode(data, data_type);
+ struct dentry *dentry;
+ char *buf = NULL, *root_buf = NULL, *name = NULL;
+ char *path, *root_path, *relative;
+ size_t file_name_len = file_name ? file_name->len : 0;
+ size_t prefix_len, root_len, len, i;
+
+ /* Name events refer to @file_name below @dir. */
+ if (file_name && dir) {
+ dentry = d_find_alias(dir);
+ } else {
+ dentry = fsnotify_data_dentry(data, data_type);
+ if (dentry)
+ dget(dentry);
+ else
+ dentry = inode ? d_find_alias(inode) : NULL;
+ }
+ if (!dentry)
+ return ERR_PTR(-ENOENT);
+
+ /* A superblock mark also reports events outside the watched tree. */
+ if (!is_subdir(dentry, root)) {
+ name = ERR_PTR(-EXDEV);
+ goto out_dput;
+ }
+
+ buf = kmalloc(PATH_MAX, KSMBD_DEFAULT_GFP);
+ root_buf = kmalloc(PATH_MAX, KSMBD_DEFAULT_GFP);
+ if (!buf || !root_buf) {
+ name = ERR_PTR(-ENOMEM);
+ goto out_free_bufs;
+ }
+
+ path = dentry_path_raw(dentry, buf, PATH_MAX);
+ root_path = dentry_path_raw(root, root_buf, PATH_MAX);
+ if (IS_ERR(path) || IS_ERR(root_path)) {
+ name = ERR_PTR(IS_ERR(path) ? PTR_ERR(path) : PTR_ERR(root_path));
+ goto out_free_bufs;
+ }
+
+ root_len = strlen(root_path);
+ if (root_len == 1 && root_path[0] == '/') {
+ relative = path + 1;
+ } else if (!strncmp(path, root_path, root_len) &&
+ (path[root_len] == '/' || path[root_len] == '\0')) {
+ relative = path + root_len;
+ if (*relative == '/')
+ relative++;
+ } else {
+ /* The watched directory was renamed between the two snapshots. */
+ name = ERR_PTR(-EAGAIN);
+ goto out_free_bufs;
+ }
+
+ prefix_len = strlen(relative);
+ if (prefix_len > SIZE_MAX - file_name_len - 2) {
+ name = ERR_PTR(-EOVERFLOW);
+ goto out_free_bufs;
+ }
+ len = prefix_len + file_name_len + (prefix_len && file_name ? 1 : 0);
+ name = kmalloc(len + 1, KSMBD_DEFAULT_GFP);
+ if (!name) {
+ name = ERR_PTR(-ENOMEM);
+ goto out_free_bufs;
+ }
+
+ memcpy(name, relative, prefix_len);
+ if (prefix_len && file_name)
+ name[prefix_len++] = '\\';
+ if (file_name_len)
+ memcpy(name + prefix_len, file_name->name, file_name_len);
+ name[len] = '\0';
+ for (i = 0; i < len; i++) {
+ if (name[i] == '/')
+ name[i] = '\\';
+ }
+ tree_name->name = name;
+ tree_name->len = len;
+
+out_free_bufs:
+ kfree(root_buf);
+ kfree(buf);
+out_dput:
+ dput(dentry);
+ return name;
+}
+
static int ksmbd_notify_handle_event(struct fsnotify_group *group, u32 mask,
const void *data, int data_type,
struct inode *dir,
@@ -119,10 +216,32 @@ static int ksmbd_notify_handle_event(struct fsnotify_group *group, u32 mask,
struct fsnotify_iter_info *iter_info)
{
struct ksmbd_notify *notify = group->private;
- struct inode *inode = fsnotify_data_inode(data, data_type);
+ struct qstr tree_name = {};
+ struct inode *inode;
+ char *name = NULL;
+ int err;
+
+ if (!READ_ONCE(notify->watch_tree)) {
+ /* Non-tree watches accept events from the root inode mark only. */
+ if (!fsnotify_iter_inode_mark(iter_info) &&
+ !fsnotify_iter_parent_mark(iter_info))
+ return 0;
+ } else {
+ name = ksmbd_notify_tree_name(notify, data, data_type, dir,
+ file_name, &tree_name);
+ if (IS_ERR(name)) {
+ if (PTR_ERR(name) == -ENOMEM)
+ pr_err("Failed to allocate tree notify event name\n");
+ return 0;
+ }
+ file_name = &tree_name;
+ }
- return ksmbd_notify_handle_inode_event(notify, mask, inode, dir,
+ inode = fsnotify_data_inode(data, data_type);
+ err = ksmbd_notify_handle_inode_event(notify, mask, inode, dir,
file_name, cookie);
+ kfree(name);
+ return err;
}
static void ksmbd_notify_free_mark(struct fsnotify_mark *mark)
@@ -166,6 +285,10 @@ ksmbd_notify_add_mark(struct ksmbd_notify *notify, u32 mask, void *obj,
static void ksmbd_notify_destroy_marks(struct ksmbd_notify *notify)
{
+ if (notify->tree_mark) {
+ fsnotify_destroy_mark(notify->tree_mark, notify->group);
+ fsnotify_put_mark(notify->tree_mark);
+ }
if (notify->mark) {
fsnotify_destroy_mark(notify->mark, notify->group);
fsnotify_put_mark(notify->mark);
@@ -175,10 +298,11 @@ static void ksmbd_notify_destroy_marks(struct ksmbd_notify *notify)
}
static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
+ bool watch_tree,
struct ksmbd_notify **notify_out)
{
struct ksmbd_notify *notify;
- struct fsnotify_mark *mark;
+ struct fsnotify_mark *mark, *tree_mark;
int err = 0;
notify = kzalloc_obj(*notify, KSMBD_DEFAULT_GFP);
@@ -192,6 +316,7 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
spin_lock_init(¬ify->lock);
notify->filter = filter;
notify->mask = mask;
+ notify->watch_tree = watch_tree;
notify->group = fsnotify_alloc_group(&ksmbd_notify_fsnotify_ops, 0);
if (IS_ERR(notify->group)) {
@@ -210,11 +335,27 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
}
notify->mark = mark;
+ if (watch_tree) {
+ /*
+ * FS_EVENT_ON_CHILD covers only one level. A filesystem mark
+ * supplies recursive events; the callback limits them to the
+ * directory rooted at fp.
+ */
+ tree_mark = ksmbd_notify_add_mark(notify, mask,
+ file_inode(fp->filp)->i_sb,
+ FSNOTIFY_OBJ_TYPE_SB);
+ if (IS_ERR(tree_mark)) {
+ err = PTR_ERR(tree_mark);
+ goto err_destroy_marks;
+ }
+ notify->tree_mark = tree_mark;
+ }
+
*notify_out = notify;
ksmbd_debug(NOTIFY,
- "Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x\n",
+ "Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x, watch tree %d\n",
(unsigned long long)file_inode(fp->filp)->i_ino, mark->mask,
- filter);
+ filter, watch_tree);
goto out;
err_destroy_marks:
@@ -309,7 +450,9 @@ ksmbd_notify_setup_watch(struct ksmbd_file *fp,
ksmbd_debug(NOTIFY,
"No mapped completion filter bits; request will remain pending\n");
- err = ksmbd_notify_add(fp, mask, filter, ¬ify);
+ err = ksmbd_notify_add(fp, mask, filter,
+ le16_to_cpu(req->Flags) & SMB2_WATCH_TREE,
+ ¬ify);
if (err) {
pr_err("Failed to add notify watch, fid %llu:%llu: %d\n",
fp->persistent_id, fp->volatile_id, 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 ` [PATCH 04/12] smb/server: support non-recursive directory change watches ChenXiaoSong
2026-09-27 10:51 ` Namjae Jeon
2026-09-28 0:38 ` ChenXiaoSong
2026-09-26 9:05 ` ChenXiaoSong [this message]
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-6-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