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 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(&notify->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, &notify);
+	err = ksmbd_notify_add(fp, mask, filter,
+			       le16_to_cpu(req->Flags) & SMB2_WATCH_TREE,
+			       &notify);
 	if (err) {
 		pr_err("Failed to add notify watch, fid %llu:%llu: %d\n",
 		       fp->persistent_id, fp->volatile_id, 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 ` [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