From: Sang-Heon Jeon <ekffu200098@gmail.com>
To: Amir Goldstein <amir73il@gmail.com>
Cc: sfrench@samba.org, pc@manguebit.org, ronniesahlberg@gmail.com,
sprasad@microsoft.com, tom@talpey.com, bharathsm@microsoft.com,
linux-cifs@vger.kernel.org,
linux-fsdevel <linux-fsdevel@vger.kernel.org>,
Jan Kara <jack@suse.cz>, Stef Bon <stefbon@gmail.com>,
Ioannis Angelakopoulos <iangelak@redhat.com>
Subject: Re: [RFC PATCH 2/2] smb: client: add directory change tracking via SMB2 Change Notify
Date: Tue, 30 Sep 2025 01:51:47 +0900 [thread overview]
Message-ID: <CABFDxMG8uLaedhFuWHLAqW75a=TFfVEHkm08uwy76B7w9xbr=w@mail.gmail.com> (raw)
In-Reply-To: <CAOQ4uxgEL=gOpSaSAV_+U=a3W5U5_Uq2Sk4agQhUpL4jHMMQ9w@mail.gmail.com>
On Sat, Sep 27, 2025 at 5:39 PM Amir Goldstein <amir73il@gmail.com> wrote:
>
> On Sat, Sep 27, 2025 at 3:03 AM Sang-Heon Jeon <ekffu200098@gmail.com> wrote:
> >
> > On Tue, Sep 16, 2025 at 10:35 PM Sang-Heon Jeon <ekffu200098@gmail.com> wrote:
> > >
> > > Implement directory change tracking using SMB2 Change Notify protocol
> > > to enable real-time monitoring of remote directories. Applications using
> > > inotify now receive immediate notifications when files are created,
> > > deleted, or renamed on SMB2+ shares.
> > >
> > > This implementation begins by detecting tracking opportunities during
> > > regular SMB operations. In the current version, the readdir() serves as
> > > the detection point - when directory contents are accessed on SMB2+
> > > servers, we check the inode's fsnotify_mask for active inotify watches.
> > > If watches exist and directory is not already being tracked, we obtain a
> > > new FiieId and send a SMB2_CHANGE_NOTIFY request that waits for changes.
> > >
> > > Based on the server's response status, we convert CHANGE_NOTIFY response
> > > to fsnotify events and deliver to application on success, mark for
> > > future reconnection on network errors, or discard the response and
> > > marked entries for broken. If tracking can continue, we resend
> > > SMB2_CHANGE_NOTIFY for continuous monitoring.
> > >
> > > Entries marked for reconnection are restored during SMB2 reconnection.
> > > In the current version, smb2_reconnect() serves as restoration point -
> > > when connection is reestablished, we obtain new FileIds and request
> > > Change Notify requests for these entries.
> > >
> > > Entries marked for unmount during unmount. In the current version,
> > > cifs_umount() serves as unmount marking point. Entries marked for clean
> > > are remove as soon as possible by the worker. Workers also run
> > > periodically; currently every 30s; to check and remove untracking
> > > directories.
> > >
> > > This feature is controlled by CONFIG_CIFS_DIR_CHANGE_TRACKING with
> > > experimental.
> > >
> > > Signed-off-by: Sang-Heon Jeon <ekffu200098@gmail.com>
> > > ---
> > > fs/smb/client/Kconfig | 17 ++
> > > fs/smb/client/Makefile | 2 +
> > > fs/smb/client/connect.c | 6 +
> > > fs/smb/client/notify.c | 535 ++++++++++++++++++++++++++++++++++++++++
> > > fs/smb/client/notify.h | 19 ++
> > > fs/smb/client/readdir.c | 9 +
> > > fs/smb/client/smb2pdu.c | 6 +
> > > 7 files changed, 594 insertions(+)
> > > create mode 100644 fs/smb/client/notify.c
> > > create mode 100644 fs/smb/client/notify.h
> > >
> > > diff --git a/fs/smb/client/Kconfig b/fs/smb/client/Kconfig
> > > index a4c02199fef4..0e3911936e0c 100644
> > > --- a/fs/smb/client/Kconfig
> > > +++ b/fs/smb/client/Kconfig
> > > @@ -218,4 +218,21 @@ config CIFS_COMPRESSION
> > > Say Y here if you want SMB traffic to be compressed.
> > > If unsure, say N.
> > >
> > > ++config CIFS_DIR_CHANGE_TRACKING
> >
> > I also found a typo here and I'll fix them.
> >
> > > + bool "Directory change tracking (Experimental)"
> > > + depends on CIFS && FSNOTIFY=y
> > > + default n
> > > + help
> > > + Enables automatic tracking of directory changes for SMB2 or later
> > > + using the SMB2 Change Notify protocol. File managers and applications
> > > + monitoring directories via inotify will receive real-time updates
> > > + when files are created, deleted, or renamed on the server.
> > > +
> > > + This feature maintains persistent connections to track changes.
> > > + Each monitored directory consumes server resources and may increase
> > > + network traffic.
> > > +
> > > + Say Y here if you want real-time directory monitoring.
> > > + If unsure, say N.
> > > +
> > > endif
> > > diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile
> > > index 4c97b31a25c2..85253bc1b4b0 100644
> > > --- a/fs/smb/client/Makefile
> > > +++ b/fs/smb/client/Makefile
> > > @@ -35,3 +35,5 @@ cifs-$(CONFIG_CIFS_ROOT) += cifsroot.o
> > > cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += smb1ops.o cifssmb.o cifstransport.o
> > >
> > > cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o compress/lz77.o
> > > +
> > > +cifs-$(CONFIG_CIFS_DIR_CHANGE_TRACKING) += notify.o
> > > diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
> > > index dd12f3eb61dc..eebf729df16a 100644
> > > --- a/fs/smb/client/connect.c
> > > +++ b/fs/smb/client/connect.c
> > > @@ -51,6 +51,9 @@
> > > #endif
> > > #include "fs_context.h"
> > > #include "cifs_swn.h"
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > +#include "notify.h"
> > > +#endif
> > >
> > > /* FIXME: should these be tunable? */
> > > #define TLINK_ERROR_EXPIRE (1 * HZ)
> > > @@ -4154,6 +4157,9 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
> > > cancel_delayed_work_sync(&cifs_sb->prune_tlinks);
> > >
> > > if (cifs_sb->master_tlink) {
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > + stop_track_sb_dir_changes(cifs_sb);
> > > +#endif
> > > tcon = cifs_sb->master_tlink->tl_tcon;
> > > if (tcon) {
> > > spin_lock(&tcon->sb_list_lock);
> > > diff --git a/fs/smb/client/notify.c b/fs/smb/client/notify.c
> > > new file mode 100644
> > > index 000000000000..e38345965744
> > > --- /dev/null
> > > +++ b/fs/smb/client/notify.c
> > > @@ -0,0 +1,535 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Directory change notification tracking for SMB
> > > + *
> > > + * Copyright (c) 2025, Sang-Heon Jeon <ekffu200098@gmail.com>
> > > + *
> > > + * References:
> > > + * MS-SMB2 "2.2.35 SMB2 CHANGE_NOTIFY Request"
> > > + * MS-SMB2 "2.2.36 SMB2 CHANGE_NOTIFY Response"
> > > + * MS-SMB2 "2.7.1 FILE_NOTIFY_INFORMATION"
> > > + * MS-SMB2 "3.3.5.19 Receiving and SMB2 CHANGE_NOTIFY Request"
> > > + * MS-FSCC "2.6 File Attributes"
> > > + */
> > > +
> > > +#include <linux/list.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/fsnotify.h>
> > > +#include "notify.h"
> > > +#include "cifsproto.h"
> > > +#include "smb2proto.h"
> > > +#include "cached_dir.h"
> > > +#include "cifs_debug.h"
> > > +#include "cifspdu.h"
> > > +#include "cifs_unicode.h"
> > > +#include "../common/smb2pdu.h"
> > > +#include "../common/smb2status.h"
> > > +
> > > +#define CLEANUP_INTERVAL (30 * HZ)
> > > +#define CLEANUP_IMMEDIATE 0
> > > +
> > > +enum notify_state {
> > > + NOTIFY_STATE_RECONNECT = BIT(0),
> > > + NOTIFY_STATE_UMOUNT = BIT(1),
> > > + NOTIFY_STATE_NOMASK = BIT(2),
> > > + NOTIFY_STATE_BROKEN_REQ = BIT(3),
> > > + NOTIFY_STATE_BROKEN_RSP = BIT(4),
> > > +};
> > > +
> > > +struct notify_info {
> > > + struct inode *inode;
> > > + const char *path;
> > > + __le16 *utf16_path;
> > > + struct cifs_fid cifs_fid;
> > > + atomic_t state;
> > > + struct list_head list;
> > > +};
> > > +
> > > +static int request_change_notify(struct notify_info *info);
> > > +static void notify_cleanup_worker(struct work_struct *work);
> > > +
> > > +static LIST_HEAD(notify_list);
> > > +static DEFINE_SPINLOCK(notify_list_lock);
> > > +static DECLARE_DELAYED_WORK(notify_cleanup_work, notify_cleanup_worker);
> > > +
> > > +static bool is_resumeable(struct notify_info *info)
> > > +{
> > > + return atomic_read(&info->state) == NOTIFY_STATE_RECONNECT;
> > > +}
> > > +
> > > +static bool is_active(struct notify_info *info)
> > > +{
> > > + return atomic_read(&info->state) == 0;
> > > +}
> > > +
> > > +static void set_state(struct notify_info *info, int state)
> > > +{
> > > + atomic_or(state, &info->state);
> > > + if (!is_resumeable(info))
> > > + mod_delayed_work(cifsiod_wq, ¬ify_cleanup_work,
> > > + CLEANUP_IMMEDIATE);
> > > +}
> > > +
> > > +static void clear_state(struct notify_info *info, int state)
> > > +{
> > > + atomic_and(~state, &info->state);
> > > +}
> > > +
> > > +static int fsnotify_send(__u32 mask,
> > > + struct inode *parent,
> > > + struct file_notify_information *fni,
> > > + u32 cookie)
> > > +{
> > > + char *name = cifs_strndup_from_utf16(fni->FileName,
> > > + le32_to_cpu(fni->FileNameLength), true,
> > > + CIFS_SB(parent->i_sb)->local_nls);
> > > + struct qstr qstr;
> > > + int rc = 0;
> > > +
> > > + if (!name)
> > > + return -ENOMEM;
> > > +
> > > + qstr.name = (const unsigned char *)name;
> > > + qstr.len = strlen(name);
> > > +
> > > + rc = fsnotify_name(mask, NULL, FSNOTIFY_EVENT_NONE, parent,
> > > + &qstr, cookie);
> > > + cifs_dbg(FYI, "fsnotify mask=%u, name=%s, cookie=%u, w/return=%d",
> > > + mask, name, cookie, rc);
> > > + kfree(name);
> > > + return rc;
> > > +}
> > > +
> > > +static bool is_fsnotify_masked(struct inode *inode)
> > > +{
> > > + if (!inode)
> > > + return false;
> > > +
> > > + /* Minimal validation of file explore inotify */
> > > + return inode->i_fsnotify_mask &
> > > + (FS_CREATE | FS_DELETE | FS_MOVED_FROM | FS_MOVED_TO);
> > > +}
> > > +
> > > +static void handle_file_notify_information(struct notify_info *info,
> > > + char *buf,
> > > + unsigned int buf_len)
> > > +{
> > > + struct file_notify_information *fni;
> > > + unsigned int next_entry_offset;
> > > + u32 cookie;
> > > + bool has_cookie = false;
> > > +
> > > + do {
> > > + fni = (struct file_notify_information *)buf;
> > > + next_entry_offset = le32_to_cpu(fni->NextEntryOffset);
> > > + if (next_entry_offset > buf_len) {
> > > + cifs_dbg(FYI, "invalid fni->NextEntryOffset=%u",
> > > + next_entry_offset);
> > > + break;
> > > + }
> > > +
> > > + switch (le32_to_cpu(fni->Action)) {
> > > + case FILE_ACTION_ADDED:
> > > + fsnotify_send(FS_CREATE, info->inode, fni, 0);
> > > + break;
> > > + case FILE_ACTION_REMOVED:
> > > + fsnotify_send(FS_DELETE, info->inode, fni, 0);
> > > + break;
> > > + case FILE_ACTION_RENAMED_OLD_NAME:
> > > + if (!has_cookie)
> > > + cookie = fsnotify_get_cookie();
> > > + has_cookie = !has_cookie;
> > > + fsnotify_send(FS_MOVED_FROM, info->inode, fni, cookie);
> > > + break;
> > > + case FILE_ACTION_RENAMED_NEW_NAME:
> > > + if (!has_cookie)
> > > + cookie = fsnotify_get_cookie();
> > > + has_cookie = !has_cookie;
> > > + fsnotify_send(FS_MOVED_TO, info->inode, fni, cookie);
> > > + break;
> > > + default:
> > > + /* Does not occur, so no need to handle */
> > > + break;
> > > + }
> > > +
> > > + buf += next_entry_offset;
> > > + buf_len -= next_entry_offset;
> > > + } while (buf_len > 0 && next_entry_offset > 0);
> > > +}
> > > +
> > > +static void handle_smb2_change_notify_rsp(struct notify_info *info,
> > > + struct mid_q_entry *mid)
> > > +{
> > > + struct smb2_change_notify_rsp *rsp = mid->resp_buf;
> > > + struct kvec rsp_iov;
> > > + unsigned int buf_offset, buf_len;
> > > + int rc;
> > > +
> > > + switch (rsp->hdr.Status) {
> > > + case STATUS_SUCCESS:
> > > + break;
> > > + case STATUS_NOTIFY_ENUM_DIR:
> > > + goto proceed;
> > > + case STATUS_USER_SESSION_DELETED:
> > > + case STATUS_NETWORK_NAME_DELETED:
> > > + case STATUS_NETWORK_SESSION_EXPIRED:
> > > + set_state(info, NOTIFY_STATE_RECONNECT);
> > > + return;
> > > + default:
> > > + set_state(info, NOTIFY_STATE_BROKEN_RSP);
> > > + return;
> > > + }
> > > +
> > > + rsp_iov.iov_base = mid->resp_buf;
> > > + rsp_iov.iov_len = mid->resp_buf_size;
> > > + buf_offset = le16_to_cpu(rsp->OutputBufferOffset);
> > > + buf_len = le32_to_cpu(rsp->OutputBufferLength);
> > > +
> > > + rc = smb2_validate_iov(buf_offset, buf_len, &rsp_iov,
> > > + sizeof(struct file_notify_information));
> > > + if (rc) {
> > > + cifs_dbg(FYI, "stay tracking, w/smb2_validate_iov=%d", rc);
> > > + goto proceed;
> > > + }
> > > +
> > > + handle_file_notify_information(info,
> > > + (char *)rsp + buf_offset, buf_len);
> > > +proceed:
> > > + request_change_notify(info);
> > > + return;
> > > +}
> > > +
> > > +static void change_notify_callback(struct mid_q_entry *mid)
> > > +{
> > > + struct notify_info *info = mid->callback_data;
> > > +
> > > + if (!is_active(info))
> > > + return;
> > > +
> > > + if (!is_fsnotify_masked(info->inode)) {
> > > + set_state(info, NOTIFY_STATE_NOMASK);
> > > + return;
> > > + }
> > > +
> > > + if (!mid->resp_buf) {
> > > + if (mid->mid_state != MID_RETRY_NEEDED) {
> > > + cifs_dbg(FYI, "stop tracking, w/mid_state=%d",
> > > + mid->mid_state);
> > > + set_state(info, NOTIFY_STATE_BROKEN_RSP);
> > > + return;
> > > + }
> > > +
> > > + set_state(info, NOTIFY_STATE_RECONNECT);
> > > + return;
> > > + }
> > > +
> > > + handle_smb2_change_notify_rsp(info, mid);
> > > +}
> > > +
> > > +static int request_change_notify(struct notify_info *info)
> > > +{
> > > + struct cifs_sb_info *cifs_sb = CIFS_SB(info->inode->i_sb);
> > > + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> > > + struct smb_rqst rqst;
> > > + struct kvec iov[1];
> > > + unsigned int xid;
> > > + int rc;
> > > +
> > > + if (!tcon) {
> > > + cifs_dbg(FYI, "missing tcon while request change notify");
> > > + return -EINVAL;
> > > + }
> > > +
> > > + memset(&rqst, 0, sizeof(struct smb_rqst));
> > > + memset(&iov, 0, sizeof(iov));
> > > + rqst.rq_iov = iov;
> > > + rqst.rq_nvec = 1;
> > > +
> > > + xid = get_xid();
> > > + rc = SMB2_notify_init(xid, &rqst, tcon, tcon->ses->server,
> > > + info->cifs_fid.persistent_fid, info->cifs_fid.volatile_fid,
> > > + FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME,
> > > + false);
> > > + free_xid(xid);
> > > + if (rc) {
> > > + set_state(info, NOTIFY_STATE_BROKEN_REQ);
> > > + return rc;
> > > + }
> > > +
> > > + rc = cifs_call_async(tcon->ses->server, &rqst, NULL,
> > > + change_notify_callback, NULL, info, 0, NULL);
> > > + cifs_small_buf_release(rqst.rq_iov[0].iov_base);
> > > +
> > > + if (rc)
> > > + set_state(info, NOTIFY_STATE_BROKEN_REQ);
> > > + return rc;
> > > +}
> > > +
> > > +
> > > +static void free_notify_info(struct notify_info *info)
> > > +{
> > > + kfree(info->utf16_path);
> > > + kfree(info->path);
> > > + kfree(info);
> > > +}
> > > +
> > > +static void cleanup_pending_mid(struct notify_info *info,
> > > + struct cifs_tcon *tcon)
> > > +{
> > > + LIST_HEAD(dispose_list);
> > > + struct TCP_Server_Info *server;
> > > + struct mid_q_entry *mid, *nmid;
> > > +
> > > + if (!tcon->ses || !tcon->ses->server)
> > > + return;
> > > +
> > > + server = tcon->ses->server;
> > > +
> > > + spin_lock(&server->mid_queue_lock);
> > > + list_for_each_entry_safe(mid, nmid, &server->pending_mid_q, qhead) {
> > > + if (mid->callback_data == info) {
> > > + mid->deleted_from_q = true;
> > > + list_move(&mid->qhead, &dispose_list);
> > > + }
> > > + }
> > > + spin_unlock(&server->mid_queue_lock);
> > > +
> > > + list_for_each_entry_safe(mid, nmid, &dispose_list, qhead) {
> > > + list_del_init(&mid->qhead);
> > > + release_mid(mid);
> > > + }
> > > +}
> > > +
> > > +static void close_fid(struct notify_info *info)
> > > +{
> > > + struct cifs_tcon *tcon;
> > > +
> > > + unsigned int xid;
> > > + int rc;
> > > +
> > > + if (!info->cifs_fid.persistent_fid && !info->cifs_fid.volatile_fid)
> > > + return;
> > > +
> > > + tcon = cifs_sb_master_tcon(CIFS_SB(info->inode->i_sb));
> > > + if (!tcon) {
> > > + cifs_dbg(FYI, "missing master tcon while close");
> > > + return;
> > > + }
> > > +
> > > + xid = get_xid();
> > > + rc = SMB2_close(xid, tcon, info->cifs_fid.persistent_fid,
> > > + info->cifs_fid.volatile_fid);
> > > + if (rc) {
> > > + cifs_dbg(FYI, "cleanup pending mid, w/SMB2_close=%d", rc);
> > > + cleanup_pending_mid(info, tcon);
> > > + }
> > > + free_xid(xid);
> > > +}
> > > +
> > > +static int setup_fid(struct notify_info *info)
> > > +{
> > > + struct cifs_sb_info *cifs_sb = CIFS_SB(info->inode->i_sb);
> > > + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> > > + struct cifs_open_parms oparms;
> > > + __u8 oplock = 0;
> > > + unsigned int xid;
> > > + int rc = 0;
> > > +
> > > + if (!tcon) {
> > > + cifs_dbg(FYI, "missing master tcon while open");
> > > + return -EINVAL;
> > > + }
> > > +
> > > + xid = get_xid();
> > > + oparms = (struct cifs_open_parms) {
> > > + .tcon = tcon,
> > > + .path = info->path,
> > > + .desired_access = GENERIC_READ,
> > > + .disposition = FILE_OPEN,
> > > + .create_options = cifs_create_options(cifs_sb, 0),
> > > + .fid = &info->cifs_fid,
> > > + .cifs_sb = cifs_sb,
> > > + .reconnect = false,
> > > + };
> > > + rc = SMB2_open(xid, &oparms, info->utf16_path, &oplock,
> > > + NULL, NULL, NULL, NULL);
> > > + free_xid(xid);
> > > + return rc;
> > > +}
> > > +
> > > +static bool is_already_tracking(struct inode *dir_inode)
> > > +{
> > > + struct notify_info *entry, *nentry;
> > > +
> > > + spin_lock(¬ify_list_lock);
> > > + list_for_each_entry_safe(entry, nentry, ¬ify_list, list) {
> > > + if (is_active(entry)) {
> > > + if (entry->inode == dir_inode) {
> > > + spin_unlock(¬ify_list_lock);
> > > + return true;
> > > + }
> > > +
> > > + /* Extra check since we must keep iterating */
> > > + if (!is_fsnotify_masked(entry->inode))
> > > + set_state(entry, NOTIFY_STATE_NOMASK);
> > > + }
> > > + }
> > > + spin_unlock(¬ify_list_lock);
> > > +
> > > + return false;
> > > +}
> > > +
> > > +static bool is_tracking_supported(struct cifs_sb_info *cifs_sb)
> > > +{
> > > + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> > > +
> > > + if (!tcon->ses || !tcon->ses->server)
> > > + return false;
> > > + return tcon->ses->server->dialect >= SMB20_PROT_ID;
> > > +}
> > > +
> > > +int start_track_dir_changes(const char *path,
> > > + struct inode *dir_inode,
> > > + struct cifs_sb_info *cifs_sb)
> > > +{
> > > + struct notify_info *info;
> > > + int rc;
> > > +
> > > + if (!is_tracking_supported(cifs_sb))
> > > + return -EINVAL;
> > > +
> > > + if (!is_fsnotify_masked(dir_inode))
> > > + return -EINVAL;
> > > +
> > > + if (is_already_tracking(dir_inode))
> > > + return 1;
> > > +
> > > + info = kzalloc(sizeof(*info), GFP_KERNEL);
> > > + if (!info)
> > > + return -ENOMEM;
> > > +
> > > + info->path = kstrdup(path, GFP_KERNEL);
> > > + if (!info->path) {
> > > + free_notify_info(info);
> > > + return -ENOMEM;
> > > + }
> > > + info->utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
> > > + if (!info->utf16_path) {
> > > + free_notify_info(info);
> > > + return -ENOMEM;
> > > + }
> > > + info->inode = dir_inode;
> > > +
> > > + rc = setup_fid(info);
> > > + if (rc) {
> > > + free_notify_info(info);
> > > + return rc;
> > > + }
> > > + rc = request_change_notify(info);
> > > + if (rc) {
> > > + close_fid(info);
> > > + free_notify_info(info);
> > > + return rc;
> > > + }
> > > +
> > > + spin_lock(¬ify_list_lock);
> > > + list_add(&info->list, ¬ify_list);
> > > + spin_unlock(¬ify_list_lock);
> > > + return rc;
> > > +}
> > > +
> > > +void stop_track_sb_dir_changes(struct cifs_sb_info *cifs_sb)
> > > +{
> > > + struct notify_info *entry, *nentry;
> > > +
> > > + if (!list_empty(¬ify_list)) {
> > > + spin_lock(¬ify_list_lock);
> > > + list_for_each_entry_safe(entry, nentry, ¬ify_list, list) {
> > > + if (cifs_sb == CIFS_SB(entry->inode->i_sb)) {
> > > + set_state(entry, NOTIFY_STATE_UMOUNT);
> > > + continue;
> > > + }
> > > +
> > > + /* Extra check since we must keep iterating */
> > > + if (!is_fsnotify_masked(entry->inode))
> > > + set_state(entry, NOTIFY_STATE_NOMASK);
> > > + }
> > > + spin_unlock(¬ify_list_lock);
> > > + }
> > > +}
> > > +
> > > +void resume_track_dir_changes(void)
> > > +{
> > > + LIST_HEAD(resume_list);
> > > + struct notify_info *entry, *nentry;
> > > + struct cifs_tcon *tcon;
> > > +
> > > + if (list_empty(¬ify_list))
> > > + return;
> > > +
> > > + spin_lock(¬ify_list_lock);
> > > + list_for_each_entry_safe(entry, nentry, ¬ify_list, list) {
> > > + if (!is_fsnotify_masked(entry->inode)) {
> > > + set_state(entry, NOTIFY_STATE_NOMASK);
> > > + continue;
> > > + }
> > > +
> > > + if (is_resumeable(entry)) {
> > > + tcon = cifs_sb_master_tcon(CIFS_SB(entry->inode->i_sb));
> > > + spin_lock(&tcon->tc_lock);
> > > + if (tcon->status == TID_GOOD) {
> > > + spin_unlock(&tcon->tc_lock);
> > > + list_move(&entry->list, &resume_list);
> > > + continue;
> > > + }
> > > + spin_unlock(&tcon->tc_lock);
> > > + }
> > > + }
> > > + spin_unlock(¬ify_list_lock);
> > > +
> > > + list_for_each_entry_safe(entry, nentry, &resume_list, list) {
> > > + if (setup_fid(entry)) {
> > > + list_del(&entry->list);
> > > + free_notify_info(entry);
> > > + continue;
> > > + }
> > > +
> > > + if (request_change_notify(entry)) {
> > > + list_del(&entry->list);
> > > + close_fid(entry);
> > > + free_notify_info(entry);
> > > + continue;
> > > + }
> > > +
> > > + clear_state(entry, NOTIFY_STATE_RECONNECT);
> > > + }
> > > +
> > > + if (!list_empty(&resume_list)) {
> > > + spin_lock(¬ify_list_lock);
> > > + list_splice(&resume_list, ¬ify_list);
> > > + spin_unlock(¬ify_list_lock);
> > > + }
> > > +}
> > > +
> > > +static void notify_cleanup_worker(struct work_struct *work)
> > > +{
> > > + LIST_HEAD(cleanup_list);
> > > + struct notify_info *entry, *nentry;
> > > +
> > > + if (list_empty(¬ify_list))
> > > + return;
> > > +
> > > + spin_lock(¬ify_list_lock);
> > > + list_for_each_entry_safe(entry, nentry, ¬ify_list, list) {
> > > + if (!is_resumeable(entry) && !is_active(entry))
> > > + list_move(&entry->list, &cleanup_list);
> > > + }
> > > + spin_unlock(¬ify_list_lock);
> > > +
> > > + list_for_each_entry_safe(entry, nentry, &cleanup_list, list) {
> > > + list_del(&entry->list);
> > > + close_fid(entry);
> > > + free_notify_info(entry);
> > > + }
> > > + mod_delayed_work(cifsiod_wq, ¬ify_cleanup_work, CLEANUP_INTERVAL);
> > > +}
> > > diff --git a/fs/smb/client/notify.h b/fs/smb/client/notify.h
> > > new file mode 100644
> > > index 000000000000..088efba4dce9
> > > --- /dev/null
> > > +++ b/fs/smb/client/notify.h
> > > @@ -0,0 +1,19 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Directory change notification tracking for SMB
> > > + *
> > > + * Copyright (c) 2025, Sang-Heon Jeon <ekffu200098@gmail.com>
> > > + */
> > > +
> > > +#ifndef _SMB_NOTIFY_H
> > > +#define _SMB_NOTIFY_H
> > > +
> > > +#include "cifsglob.h"
> > > +
> > > +int start_track_dir_changes(const char *path,
> > > + struct inode *dir_inode,
> > > + struct cifs_sb_info *cifs_sb);
> > > +void stop_track_sb_dir_changes(struct cifs_sb_info *cifs_sb);
> > > +void resume_track_dir_changes(void);
> > > +
> > > +#endif /* _SMB_NOTIFY_H */
> > > diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
> > > index 4e5460206397..455e5be37116 100644
> > > --- a/fs/smb/client/readdir.c
> > > +++ b/fs/smb/client/readdir.c
> > > @@ -24,6 +24,9 @@
> > > #include "fs_context.h"
> > > #include "cached_dir.h"
> > > #include "reparse.h"
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > +#include "notify.h"
> > > +#endif
> > >
> > > /*
> > > * To be safe - for UCS to UTF-8 with strings loaded with the rare long
> > > @@ -1070,6 +1073,9 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
> > > if (rc)
> > > goto cache_not_found;
> > >
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > + start_track_dir_changes(full_path, d_inode(file_dentry(file)), cifs_sb);
> > > +#endif
> > > mutex_lock(&cfid->dirents.de_mutex);
> > > /*
> > > * If this was reading from the start of the directory
> > > @@ -1151,6 +1157,9 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
> > > cifs_dbg(FYI, "Could not find entry\n");
> > > goto rddir2_exit;
> > > }
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > + start_track_dir_changes(full_path, d_inode(file_dentry(file)), cifs_sb);
> > > +#endif
> > > cifs_dbg(FYI, "loop through %d times filling dir for net buf %p\n",
> > > num_to_fill, cifsFile->srch_inf.ntwrk_buf_start);
> > > max_len = tcon->ses->server->ops->calc_smb_size(
> > > diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
> > > index 4e922cb32110..58a1ddc39ee6 100644
> > > --- a/fs/smb/client/smb2pdu.c
> > > +++ b/fs/smb/client/smb2pdu.c
> > > @@ -45,6 +45,9 @@
> > > #include "cached_dir.h"
> > > #include "compress.h"
> > > #include "fs_context.h"
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > +#include "notify.h"
> > > +#endif
> > >
> > > /*
> > > * The following table defines the expected "StructureSize" of SMB2 requests
> > > @@ -466,6 +469,9 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
> > > mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
> > >
> > > atomic_inc(&tconInfoReconnectCount);
> > > +#ifdef CONFIG_CIFS_DIR_CHANGE_TRACKING
> > > + resume_track_dir_changes();
> > > +#endif
> > > out:
> > > /*
> > > * Check if handle based operation so we know whether we can continue
> > > --
> > > 2.43.0
> > >
> >
> > To Reviewers.
> >
> > This is a gentle reminder on review requests.
> >
> > I would be very grateful for your feedback and am more than willing to
> > revise or improve any parts as needed. And also, let me know if I
> > missed anything or made mistakes.
>
> Hi Sang,
Hello, Amir
> First feedback (value):
> -----------------------------
> This looks very useful. this feature has been requested and
> attempted several times in the past (see links below), so if you are
> willing to incorporate feedback, I hope you will reach further than those
> past attempts and I will certainly do my best to help you with that.
Thanks for your kind comment. I'm really glad to hear that.
> Second feedback (reviewers):
> ----------------------------------------
> I was very surprised that your patch doesn't touch any vfs code
> (more on that on design feedback), but this is not an SMB-contained
> change at all.
I agree with your last comment. I think it might not be easy;
honestly, I may know less than
Ioannis or Vivek; but I'm fully committed to giving it a try, no
matter the challenge.
> Your patch touches the guts of the fsnotify subsystem (in a wrong way).
> For the next posting please consult the MAINTAINERS entry
> of the fsnotify subsystem for reviewers and list to CC (now added).
I see. I'll keep it in my mind.
> Third feedback (design):
> --------------------------------
> The design choice of polling i_fsnotify_mask on readdir()
> is quite odd and it is not clear to me why it makes sense.
> Previous discussions suggested to have a filesystem method
> to update when applications setup a watch on a directory [1].
> Another prior feedback was that the API should allow a clear
> distinction between the REMOTE notifications and the LOCAL
> notifications [2][3].
Current design choice is a workaround for setting an appropriate add
watch point (as well as remove). I don't want to stick to the RFC
design. Also, The point that I considered important is similar to
Ioannis' one: compatible with existing applications.
> IMO it would be better to finalize the design before working on the
> code, but that's up to you.
I agree, although it's quite hard to create a perfect blueprint, but
it might be better to draw to some extent.
Based on my current understanding, I think we need to do the following things.
- design more compatible and general fsnotify API for all network fs;
should process LOCAL and REMOTE both smoothly.
- expand inotify (if needed, fanotify both) flow with new fsnotify API
- replace SMB2 change_notify start/end point to new API
Let me know if I missed or misunderstood something. And also please
give me some time to read attached threads more deeply and clean up my
thoughts and questions.
> Good luck,
> Amir.
>
> [1] https://lore.kernel.org/linux-fsdevel/20211025204634.2517-4-iangelak@redhat.com/
> [2] https://sourceforge.net/p/fuse/mailman/message/58719889/
> [3] https://lore.kernel.org/linux-fsdevel/YhpY%2FzZe3UA2u3fj@redhat.com/
Best Regards,
Sang-Heon Jeon
next prev parent reply other threads:[~2025-09-29 16:52 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20250916133437.713064-1-ekffu200098@gmail.com>
[not found] ` <20250916133437.713064-3-ekffu200098@gmail.com>
[not found] ` <CABFDxMFtZKSr5KbqcGQzJWYwT5URUYeuEHJ1a_jDUQPO-OKVGg@mail.gmail.com>
2025-09-27 8:39 ` [RFC PATCH 2/2] smb: client: add directory change tracking via SMB2 Change Notify Amir Goldstein
2025-09-29 16:51 ` Sang-Heon Jeon [this message]
2025-09-29 17:06 ` Amir Goldstein
2025-10-17 4:05 ` Sang-Heon Jeon
2025-10-23 15:30 ` Amir Goldstein
2025-10-28 16:13 ` Sang-Heon Jeon
2025-10-29 12:39 ` Amir Goldstein
2025-10-29 17:13 ` Sang-Heon Jeon
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='CABFDxMG8uLaedhFuWHLAqW75a=TFfVEHkm08uwy76B7w9xbr=w@mail.gmail.com' \
--to=ekffu200098@gmail.com \
--cc=amir73il@gmail.com \
--cc=bharathsm@microsoft.com \
--cc=iangelak@redhat.com \
--cc=jack@suse.cz \
--cc=linux-cifs@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=pc@manguebit.org \
--cc=ronniesahlberg@gmail.com \
--cc=sfrench@samba.org \
--cc=sprasad@microsoft.com \
--cc=stefbon@gmail.com \
--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;
as well as URLs for NNTP newsgroup(s).