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]>
---
 fs/nfsd/filecache.c    |  51 ++++++++++----
 fs/nfsd/nfs4callback.c |  19 +++--
 fs/nfsd/nfs4state.c    | 185 +++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfsd/nfs4xdr.c      |  95 +++++++++++++++++++++++++
 fs/nfsd/state.h        |   2 +
 fs/nfsd/xdr4.h         |   2 +
 6 files changed, 337 insertions(+), 17 deletions(-)

diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 
0b9ee6c6baa89a306c88c56d8a7b80b5683c03e3..dffa67a6ed50905be99ffca73b0a38a69e96d1b9
 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -72,6 +72,7 @@ static struct kmem_cache              *nfsd_file_mark_slab;
 static struct list_lru                 nfsd_file_lru;
 static unsigned long                   nfsd_file_flags;
 static struct fsnotify_group           *nfsd_file_fsnotify_group;
+static struct fsnotify_group           *nfsd_dir_fsnotify_group;
 static struct delayed_work             nfsd_filecache_laundrette;
 static struct rhltable                 nfsd_file_rhltable
                                                ____cacheline_aligned_in_smp;
@@ -147,7 +148,7 @@ static void
 nfsd_file_mark_put(struct nfsd_file_mark *nfm)
 {
        if (refcount_dec_and_test(&nfm->nfm_ref)) {
-               fsnotify_destroy_mark(&nfm->nfm_mark, nfsd_file_fsnotify_group);
+               fsnotify_destroy_mark(&nfm->nfm_mark, nfm->nfm_mark.group);
                fsnotify_put_mark(&nfm->nfm_mark);
        }
 }
@@ -155,35 +156,37 @@ nfsd_file_mark_put(struct nfsd_file_mark *nfm)
 static struct nfsd_file_mark *
 nfsd_file_mark_find_or_create(struct inode *inode)
 {
-       int                     err;
-       struct fsnotify_mark    *mark;
        struct nfsd_file_mark   *nfm = NULL, *new;
+       struct fsnotify_group   *group;
+       struct fsnotify_mark    *mark;
+       int                     err;
+
+       group = S_ISDIR(inode->i_mode) ? nfsd_dir_fsnotify_group : 
nfsd_file_fsnotify_group;
 
        do {
-               fsnotify_group_lock(nfsd_file_fsnotify_group);
-               mark = fsnotify_find_inode_mark(inode,
-                                               nfsd_file_fsnotify_group);
+               fsnotify_group_lock(group);
+               mark = fsnotify_find_inode_mark(inode, group);
                if (mark) {
                        nfm = nfsd_file_mark_get(container_of(mark,
                                                 struct nfsd_file_mark,
                                                 nfm_mark));
-                       fsnotify_group_unlock(nfsd_file_fsnotify_group);
+                       fsnotify_group_unlock(group);
                        if (nfm) {
                                fsnotify_put_mark(mark);
                                break;
                        }
                        /* Avoid soft lockup race with nfsd_file_mark_put() */
-                       fsnotify_destroy_mark(mark, nfsd_file_fsnotify_group);
+                       fsnotify_destroy_mark(mark, group);
                        fsnotify_put_mark(mark);
                } else {
-                       fsnotify_group_unlock(nfsd_file_fsnotify_group);
+                       fsnotify_group_unlock(group);
                }
 
                /* allocate a new nfm */
                new = kmem_cache_alloc(nfsd_file_mark_slab, GFP_KERNEL);
                if (!new)
                        return NULL;
-               fsnotify_init_mark(&new->nfm_mark, nfsd_file_fsnotify_group);
+               fsnotify_init_mark(&new->nfm_mark, group);
                new->nfm_mark.mask = FS_ATTRIB|FS_DELETE_SELF;
                refcount_set(&new->nfm_ref, 1);
 
@@ -763,12 +766,25 @@ nfsd_file_fsnotify_handle_event(struct fsnotify_mark 
*mark, u32 mask,
        return 0;
 }
 
+static int
+nfsd_dir_fsnotify_handle_event(struct fsnotify_group *group, u32 mask,
+                              const void *data, int data_type, struct inode 
*dir,
+                              const struct qstr *name, u32 cookie,
+                              struct fsnotify_iter_info *iter_info)
+{
+       return nfsd_handle_dir_event(mask, dir, data, data_type, name);
+}
 
 static const struct fsnotify_ops nfsd_file_fsnotify_ops = {
        .handle_inode_event = nfsd_file_fsnotify_handle_event,
        .free_mark = nfsd_file_mark_free,
 };
 
+static const struct fsnotify_ops nfsd_dir_fsnotify_ops = {
+       .handle_event = nfsd_dir_fsnotify_handle_event,
+       .free_mark = nfsd_file_mark_free,
+};
+
 int
 nfsd_file_cache_init(void)
 {
@@ -820,8 +836,7 @@ nfsd_file_cache_init(void)
                goto out_shrinker;
        }
 
-       nfsd_file_fsnotify_group = fsnotify_alloc_group(&nfsd_file_fsnotify_ops,
-                                                       0);
+       nfsd_file_fsnotify_group = 
fsnotify_alloc_group(&nfsd_file_fsnotify_ops, 0);
        if (IS_ERR(nfsd_file_fsnotify_group)) {
                pr_err("nfsd: unable to create fsnotify group: %ld\n",
                        PTR_ERR(nfsd_file_fsnotify_group));
@@ -830,11 +845,23 @@ nfsd_file_cache_init(void)
                goto out_notifier;
        }
 
+       nfsd_dir_fsnotify_group = fsnotify_alloc_group(&nfsd_dir_fsnotify_ops, 
0);
+       if (IS_ERR(nfsd_dir_fsnotify_group)) {
+               pr_err("nfsd: unable to create fsnotify group: %ld\n",
+                       PTR_ERR(nfsd_dir_fsnotify_group));
+               ret = PTR_ERR(nfsd_dir_fsnotify_group);
+               nfsd_dir_fsnotify_group = NULL;
+               goto out_notify_group;
+       }
+
        INIT_DELAYED_WORK(&nfsd_filecache_laundrette, nfsd_file_gc_worker);
 out:
        if (ret)
                clear_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags);
        return ret;
+out_notify_group:
+       fsnotify_put_group(nfsd_file_fsnotify_group);
+       nfsd_file_fsnotify_group = NULL;
 out_notifier:
        lease_unregister_notifier(&nfsd_file_lease_notifier);
 out_shrinker:
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 
fe7b20b94d76efd309e27c1a3ef359e7101dac80..ee85ff54895d46fd25e73b5f96f3b467eff41286
 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -870,21 +870,30 @@ static void nfs4_xdr_enc_cb_notify(struct rpc_rqst *req,
                                   const void *data)
 {
        const struct nfsd4_callback *cb = data;
+       struct nfsd4_cb_notify *ncn = container_of(cb, struct nfsd4_cb_notify, 
ncn_cb);
+       struct nfs4_delegation *dp = container_of(ncn, struct nfs4_delegation, 
dl_cb_notify);
        struct nfs4_cb_compound_hdr hdr = {
                .ident = 0,
                .minorversion = cb->cb_clp->cl_minorversion,
        };
-       struct CB_NOTIFY4args args = { };
+       struct CB_NOTIFY4args args;
+       __be32 *p;
 
        WARN_ON_ONCE(hdr.minorversion == 0);
 
        encode_cb_compound4args(xdr, &hdr);
        encode_cb_sequence4args(xdr, cb, &hdr);
 
-       /*
-        * FIXME: get stateid and fh from delegation. Inline the cna_changes
-        * buffer, and zero it.
-        */
+       p = xdr_reserve_space(xdr, 4);
+       *p = cpu_to_be32(OP_CB_NOTIFY);
+
+       args.cna_stateid.seqid = dp->dl_stid.sc_stateid.si_generation;
+       memcpy(&args.cna_stateid.other, &dp->dl_stid.sc_stateid.si_opaque,
+              ARRAY_SIZE(args.cna_stateid.other));
+       args.cna_fh.len = dp->dl_stid.sc_file->fi_fhandle.fh_size;
+       args.cna_fh.data = dp->dl_stid.sc_file->fi_fhandle.fh_raw;
+       args.cna_changes.count = ncn->ncn_nf_cnt;
+       args.cna_changes.element = ncn->ncn_nf;
        WARN_ON_ONCE(!xdrgen_encode_CB_NOTIFY4args(xdr, &args));
 
        hdr.nops++;
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 
5d3af33e70e26e59f8bc3d5b44c82beafb4f786b..f3d3e3faf7d5f1b2ee39abb7eabd2fa406bbac21
 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -55,6 +55,7 @@
 #include "netns.h"
 #include "pnfs.h"
 #include "filecache.h"
+#include "nfs4xdr_gen.h"
 #include "trace.h"
 
 #define NFSDDBG_FACILITY                NFSDDBG_PROC
@@ -3333,15 +3334,92 @@ nfsd4_cb_getattr_release(struct nfsd4_callback *cb)
        nfs4_put_stid(&dp->dl_stid);
 }
 
+static bool
+nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
+{
+       struct nfsd4_cb_notify *ncn = container_of(cb, struct nfsd4_cb_notify, 
ncn_cb);
+       struct nfs4_delegation *dp = container_of(ncn, struct nfs4_delegation, 
dl_cb_notify);
+       struct nfsd_notify_event *events[NOTIFY4_EVENT_QUEUE_SIZE];
+       struct xdr_buf xdr = { .buflen = PAGE_SIZE * NOTIFY4_PAGE_ARRAY_SIZE,
+                              .pages  = ncn->ncn_pages };
+       struct xdr_stream stream;
+       int count, i;
+       bool error = false;
+
+       xdr_init_encode_pages(&stream, &xdr);
+
+       spin_lock(&ncn->ncn_lock);
+       count = ncn->ncn_evt_cnt;
+
+       /* spurious queueing? */
+       if (count == 0) {
+               spin_unlock(&ncn->ncn_lock);
+               return false;
+       }
+
+       /* we can't keep up! */
+       if (count > NOTIFY4_EVENT_QUEUE_SIZE) {
+               spin_unlock(&ncn->ncn_lock);
+               goto out_recall;
+       }
+
+       memcpy(events, ncn->ncn_evt, sizeof(*events) * count);
+       ncn->ncn_evt_cnt = 0;
+       spin_unlock(&ncn->ncn_lock);
+
+       for (i = 0; i < count; ++i) {
+               struct nfsd_notify_event *nne = events[i];
+
+               if (!error) {
+                       u32 *maskp = (u32 *)xdr_reserve_space(&stream, 
sizeof(*maskp));
+                       u8 *p;
+
+                       if (!maskp) {
+                               error = true;
+                               goto put_event;
+                       }
+
+                       p = nfsd4_encode_notify_event(&stream, nne, dp, maskp);
+                       if (!p) {
+                               pr_notice("Count not generate CB_NOTIFY from 
fsnotify mask 0x%x\n",
+                                         nne->ne_mask);
+                               error = true;
+                               goto put_event;
+                       }
+
+                       ncn->ncn_nf[i].notify_mask.count = 1;
+                       ncn->ncn_nf[i].notify_mask.element = maskp;
+                       ncn->ncn_nf[i].notify_vals.data = p;
+                       ncn->ncn_nf[i].notify_vals.len = (u8 *)stream.p - p;
+               }
+put_event:
+               nfsd_notify_event_put(nne);
+       }
+       if (!error) {
+               ncn->ncn_nf_cnt = count;
+               return true;
+       }
+out_recall:
+       nfsd4_run_cb(&dp->dl_recall);
+       return false;
+}
+
 static int
 nfsd4_cb_notify_done(struct nfsd4_callback *cb,
                                struct rpc_task *task)
 {
+       struct nfsd4_cb_notify *ncn = container_of(cb, struct nfsd4_cb_notify, 
ncn_cb);
+       struct nfs4_delegation *dp = container_of(ncn, struct nfs4_delegation, 
dl_cb_notify);
+
        switch (task->tk_status) {
        case -NFS4ERR_DELAY:
                rpc_delay(task, 2 * HZ);
                return 0;
        default:
+               /* For any other hard error, recall the deleg */
+               nfsd4_run_cb(&dp->dl_recall);
+               fallthrough;
+       case 0:
                return 1;
        }
 }
@@ -3370,6 +3448,7 @@ static const struct nfsd4_callback_ops 
nfsd4_cb_getattr_ops = {
 };
 
 static const struct nfsd4_callback_ops nfsd4_cb_notify_ops = {
+       .prepare        = nfsd4_cb_notify_prepare,
        .done           = nfsd4_cb_notify_done,
        .release        = nfsd4_cb_notify_release,
        .opcode         = OP_CB_NOTIFY,
@@ -9645,3 +9724,109 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
        put_deleg_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 + 1] = '\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_dir_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;
+
+       return true;
+}
+
+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_dir_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;
+
+       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);
+       return 0;
+}
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 
97e9e9afa80af50a5f6eda19a6eb2cb3cf013f32..4a40d5e07fa3343a9b645c3b267897a31491e8e9
 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -3752,6 +3752,101 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct 
xdr_stream *xdr,
        goto out;
 }
 
+static bool
+nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
+                         struct dentry *dentry, struct nfs4_delegation *dp,
+                         char *name, u32 namelen)
+{
+       uint32_t *attrmask;
+
+       /* Reserve space for attrmask */
+       attrmask = xdr_reserve_space(xdr, 3 * sizeof(uint32_t));
+       if (!attrmask)
+               return false;
+
+       ne->ne_file.data = name;
+       ne->ne_file.len = namelen;
+       ne->ne_attrs.attrmask.element = attrmask;
+
+       attrmask[0] = 0;
+       attrmask[1] = 0;
+       attrmask[2] = 0;
+       ne->ne_attrs.attr_vals.data = NULL;
+       ne->ne_attrs.attr_vals.len = 0;
+       ne->ne_attrs.attrmask.count = 1;
+       return true;
+}
+
+/**
+ * nfsd4_encode_notify_event - encode a notify
+ * @xdr: stream to which to encode the fattr4
+ * @nne: nfsd_notify_event to encode
+ * @dp: delegation where the event occurred
+ * @notify_mask: pointer to word where notification mask should be set
+ *
+ * Encode @nne into @xdr. Returns a pointer to the start of the event, or NULL 
if
+ * the event couldn't be encoded. The appropriate bit in the notify_mask will 
also
+ * be set on success.
+ */
+u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event 
*nne,
+                             struct nfs4_delegation *dp, u32 *notify_mask)
+{
+       u8 *p = NULL;
+
+       *notify_mask = 0;
+
+       if (nne->ne_mask & FS_DELETE) {
+               struct notify_remove4 nr = { };
+
+               if (!nfsd4_setup_notify_entry4(&nr.nrm_old_entry, xdr, 
nne->ne_dentry, dp,
+                                              nne->ne_name, nne->ne_namelen))
+                       goto out_err;
+               p = (u8 *)xdr->p;
+               if (!xdrgen_encode_notify_remove4(xdr, &nr))
+                       goto out_err;
+               *notify_mask |= BIT(NOTIFY4_REMOVE_ENTRY);
+       } else if (nne->ne_mask & FS_CREATE) {
+               struct notify_add4 na = { };
+
+               if (!nfsd4_setup_notify_entry4(&na.nad_new_entry, xdr, 
nne->ne_dentry, dp,
+                                              nne->ne_name, nne->ne_namelen))
+                       goto out_err;
+
+               p = (u8 *)xdr->p;
+               if (!xdrgen_encode_notify_add4(xdr, &na))
+                       goto out_err;
+
+               *notify_mask |= BIT(NOTIFY4_ADD_ENTRY);
+       } else if (nne->ne_mask & FS_RENAME) {
+               struct notify_rename4 nr = { };
+               struct name_snapshot n;
+               bool ret;
+
+               /* Don't send any attributes in the old_entry since they're the 
same in new */
+               if (!nfsd4_setup_notify_entry4(&nr.nrn_old_entry.nrm_old_entry, 
xdr,
+                                              NULL, dp, nne->ne_name,
+                                              nne->ne_namelen))
+                       goto out_err;
+
+               take_dentry_name_snapshot(&n, nne->ne_dentry);
+               ret = 
nfsd4_setup_notify_entry4(&nr.nrn_new_entry.nad_new_entry, xdr,
+                                              nne->ne_dentry, dp, (char 
*)n.name.name,
+                                              n.name.len);
+               if (ret) {
+                       p = (u8 *)xdr->p;
+                       ret = xdrgen_encode_notify_rename4(xdr, &nr);
+               }
+               release_dentry_name_snapshot(&n);
+               if (!ret)
+                       goto out_err;
+               *notify_mask |= BIT(NOTIFY4_RENAME_ENTRY);
+       }
+       return p;
+out_err:
+       pr_warn("nfsd: unable to marshal notify_rename4 to xdr stream\n");
+       return NULL;
+}
+
 static void svcxdr_init_encode_from_buffer(struct xdr_stream *xdr,
                                struct xdr_buf *buf, __be32 *p, int bytes)
 {
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 
507ce4c097c8b601eecb040876412fc2fe3033b2..232b64e1d7721d3074364ff788b4f72b02a6c63f
 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -877,6 +877,8 @@ bool nfsd4_has_active_async_copies(struct nfs4_client *clp);
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(struct xdr_netobj 
name,
                                struct xdr_netobj princhash, struct nfsd_net 
*nn);
 extern bool nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net 
*nn);
+int nfsd_handle_dir_event(u32 mask, const struct inode *dir, const void *data,
+                         int data_type, const struct qstr *name);
 
 void put_nfs4_file(struct nfs4_file *fi);
 extern void nfs4_put_cpntf_state(struct nfsd_net *nn,
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index 
d4b48602b2b0c3854473e8a5812652815ab26c12..19f468b5bc54343dca928f0b8286c868f2133241
 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -967,6 +967,8 @@ __be32 nfsd4_encode_fattr_to_buf(__be32 **p, int words,
                struct svc_fh *fhp, struct svc_export *exp,
                struct dentry *dentry,
                u32 *bmval, struct svc_rqst *, int ignore_crossmnt);
+u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event 
*nne,
+                             struct nfs4_delegation *dd, u32 *notify_mask);
 extern __be32 nfsd4_setclientid(struct svc_rqst *rqstp,
                struct nfsd4_compound_state *, union nfsd4_op_u *u);
 extern __be32 nfsd4_setclientid_confirm(struct svc_rqst *rqstp,

-- 
2.51.0


Reply via email to