On Wed, 2026-04-08 at 14:34 -0400, Chuck Lever wrote:
>
> On Tue, Apr 7, 2026, at 9:21 AM, Jeff Layton wrote:
> > Add the necessary parts to accept a fsnotify callback for directory
> > change event and create a CB_NOTIFY request for it. When a dir nfsd_file
> > is created set a handle_event callback to handle the notification.
> >
> > Use that to allocate a nfsd_notify_event object and then hand off a
> > reference to each delegation's CB_NOTIFY. If anything fails along the
> > way, recall any affected delegations.
> >
> > Signed-off-by: Jeff Layton <[email protected]>
>
> > diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> > index b2b8c454fc0f..339c3d0bb575 100644
> > --- a/fs/nfsd/nfs4state.c
> > +++ b/fs/nfsd/nfs4state.c
>
> > @@ -9796,3 +9887,118 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state
> > *cstate,
> > put_nfs4_file(fp);
> > return ERR_PTR(status);
> > }
> > +
> > +static void
> > +nfsd4_run_cb_notify(struct nfsd4_cb_notify *ncn)
> > +{
> > + struct nfs4_delegation *dp = container_of(ncn, struct
> > nfs4_delegation, dl_cb_notify);
> > +
> > + if (test_and_set_bit(NFSD4_CALLBACK_RUNNING, &ncn->ncn_cb.cb_flags))
> > + return;
> > +
> > + if (!refcount_inc_not_zero(&dp->dl_stid.sc_count))
> > + clear_bit(NFSD4_CALLBACK_RUNNING, &ncn->ncn_cb.cb_flags);
> > + else
> > + nfsd4_run_cb(&ncn->ncn_cb);
> > +}
> > +
> > +static struct nfsd_notify_event *
> > +alloc_nfsd_notify_event(u32 mask, const struct qstr *q, struct dentry
> > *dentry)
> > +{
> > + struct nfsd_notify_event *ne;
> > +
> > + ne = kmalloc(sizeof(*ne) + q->len + 1, GFP_KERNEL);
> > + if (!ne)
> > + return NULL;
> > +
> > + memcpy(&ne->ne_name, q->name, q->len);
> > + refcount_set(&ne->ne_ref, 1);
> > + ne->ne_mask = mask;
> > + ne->ne_name[q->len] = '\0';
> > + ne->ne_namelen = q->len;
> > + ne->ne_dentry = dget(dentry);
> > + return ne;
> > +}
> > +
> > +static bool
> > +should_notify_deleg(u32 mask, struct file_lease *fl)
> > +{
> > + /* Only nfsd leases */
> > + if (fl->fl_lmops != &nfsd_lease_mng_ops)
> > + return false;
> > +
> > + /* Skip if this event wasn't ignored by the lease */
> > + if ((mask & FS_DELETE) && !(fl->c.flc_flags & FL_IGN_DIR_DELETE))
> > + return false;
> > + if ((mask & FS_CREATE) && !(fl->c.flc_flags & FL_IGN_DIR_CREATE))
> > + return false;
> > + if ((mask & FS_RENAME) && !(fl->c.flc_flags & FL_IGN_DIR_RENAME))
> > + return false;
> > +
> > + return true;
> > +}
>
> For a cross-directory rename, vfs_rename calls try_break_deleg(old_dir,
> LEASE_BREAK_DIR_DELETE, ...). A delegation with FL_IGN_DIR_DELETE
> (subscribed to NOTIFY4_REMOVE_ENTRY) suppresses the lease break, which
> is correct.
>
> But fsnotify delivers FS_RENAME on old_dir, not FS_DELETE. In
> should_notify_deleg(), the check (mask & FS_RENAME) &&
> !(fl->c.flc_flags & FL_IGN_DIR_RENAME) fails, because the delegation
> has FL_IGN_DIR_DELETE but not FL_IGN_DIR_RENAME. No notification is
> sent.
>
Actually, it delivers FS_RENAME_FROM to the old directory and
FS_RENAME_TO to the new one in this case, AFAICT. Basically, we need to
watch for those in addition to FS_CREATE/DELETE and treat them the same
way. I'm looking at a fix now. I'll also see about adding a pynfs test
to do a cross directory rename with notifications. I don't think I have
that now.
Thanks!
> IIUC, a client subscribed to NOTIFY4_REMOVE_ENTRY for old_dir sees
> neither a lease break nor a CB_NOTIFY when a child is renamed out of
> the directory. Is that behavior correct?
>
>
> > +
> > +static void
> > +nfsd_recall_all_dir_delegs(const struct inode *dir)
> > +{
> > + struct file_lock_context *ctx = locks_inode_context(dir);
> > + struct file_lock_core *flc;
> > +
> > + spin_lock(&ctx->flc_lock);
> > + list_for_each_entry(flc, &ctx->flc_lease, flc_list) {
> > + struct file_lease *fl = container_of(flc, struct file_lease, c);
> > +
> > + if (fl->fl_lmops == &nfsd_lease_mng_ops)
> > + nfsd_break_deleg_cb(fl);
> > + }
> > + spin_unlock(&ctx->flc_lock);
> > +}
> > +
> > +int
> > +nfsd_handle_dir_event(u32 mask, const struct inode *dir, const void
> > *data,
> > + int data_type, const struct qstr *name)
> > +{
> > + struct dentry *dentry = fsnotify_data_dentry(data, data_type);
> > + struct file_lock_context *ctx;
> > + struct file_lock_core *flc;
> > + struct nfsd_notify_event *evt;
> > +
> > + /* Don't do anything if this is not an expected event */
> > + if (!(mask & (FS_CREATE|FS_DELETE|FS_RENAME)))
> > + return 0;
> > +
> > + ctx = locks_inode_context(dir);
> > + if (!ctx || list_empty(&ctx->flc_lease))
> > + return 0;
> > +
> > + evt = alloc_nfsd_notify_event(mask, name, dentry);
> > + if (!evt) {
> > + nfsd_recall_all_dir_delegs(dir);
> > + return 0;
> > + }
> > +
> > + spin_lock(&ctx->flc_lock);
> > + list_for_each_entry(flc, &ctx->flc_lease, flc_list) {
> > + struct file_lease *fl = container_of(flc, struct file_lease, c);
> > + struct nfs4_delegation *dp = flc->flc_owner;
> > + struct nfsd4_cb_notify *ncn = &dp->dl_cb_notify;
> > +
> > + if (!should_notify_deleg(mask, fl))
> > + continue;
> > +
> > + spin_lock(&ncn->ncn_lock);
> > + if (ncn->ncn_evt_cnt >= NOTIFY4_EVENT_QUEUE_SIZE) {
> > + /* We're generating notifications too fast. Recall. */
> > + spin_unlock(&ncn->ncn_lock);
> > + nfsd_break_deleg_cb(fl);
> > + continue;
> > + }
> > + ncn->ncn_evt[ncn->ncn_evt_cnt++] = nfsd_notify_event_get(evt);
> > + spin_unlock(&ncn->ncn_lock);
> > +
> > + nfsd4_run_cb_notify(ncn);
> > + }
> > + spin_unlock(&ctx->flc_lock);
> > + nfsd_notify_event_put(evt);
> > + return 0;
> > +}
>
--
Jeff Layton <[email protected]>