mirror of
https://github.com/torvalds/linux.git
synced 2026-09-13 15:40:03 +02:00
nfsd: add support to CB_NOTIFY for dir attribute changes
If the client requested dir attribute change notifications, send those alongside any set of add/remove/rename events. Note that the server will still recall the delegation on a SETATTR, so these are only sent for changes to child dirents. Signed-off-by: Jeff Layton <jlayton@kernel.org> [ cel: folded "nfsd: fix CB_NOTIFY workqueue loop when queue overflows" ] [ cel: folded "nfsd: recall deleg if a requested dir attr change can't be encoded" ] Link: https://patch.msgid.link/20260616-dir-deleg-v7-20-6cbc7eac0ade@kernel.org Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
parent
ac36b75864
commit
4ae3a720f9
|
|
@ -464,6 +464,7 @@ pragma public notify_add4;
|
|||
struct notify_attr4 {
|
||||
notify_entry4 na_changed_entry;
|
||||
};
|
||||
pragma public notify_attr4;
|
||||
|
||||
struct notify_rename4 {
|
||||
notify_remove4 nrn_old_entry;
|
||||
|
|
|
|||
|
|
@ -2552,9 +2552,10 @@ nfsd4_verify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
|
|||
return status == nfserr_same ? nfs_ok : status;
|
||||
}
|
||||
|
||||
#define SUPPORTED_NOTIFY_MASK (BIT(NOTIFY4_REMOVE_ENTRY) | \
|
||||
BIT(NOTIFY4_ADD_ENTRY) | \
|
||||
BIT(NOTIFY4_RENAME_ENTRY) | \
|
||||
#define SUPPORTED_NOTIFY_MASK (BIT(NOTIFY4_CHANGE_DIR_ATTRS) | \
|
||||
BIT(NOTIFY4_REMOVE_ENTRY) | \
|
||||
BIT(NOTIFY4_ADD_ENTRY) | \
|
||||
BIT(NOTIFY4_RENAME_ENTRY) | \
|
||||
BIT(NOTIFY4_GFLAG_EXTEND))
|
||||
|
||||
static __be32
|
||||
|
|
|
|||
|
|
@ -3522,10 +3522,15 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
|
|||
struct nfsd_notify_event *events[NOTIFY4_EVENT_QUEUE_SIZE];
|
||||
struct xdr_buf xdr = { .buflen = PAGE_SIZE * NOTIFY4_PAGE_ARRAY_SIZE,
|
||||
.pages = ncn->ncn_pages };
|
||||
int limit = NOTIFY4_EVENT_QUEUE_SIZE;
|
||||
struct xdr_stream stream;
|
||||
struct nfsd_file *nf;
|
||||
int count, i;
|
||||
bool error = false;
|
||||
int count, i;
|
||||
|
||||
/* Save a slot for dir attr update if requested */
|
||||
if (dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS))
|
||||
--limit;
|
||||
|
||||
/* Clear any failure recorded by a previous transmit. */
|
||||
ncn->ncn_encode_err = false;
|
||||
|
|
@ -3541,16 +3546,21 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
|
|||
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);
|
||||
|
||||
/*
|
||||
* We can't keep up! Drop the queued events and recall. The queue must
|
||||
* be drained here: out_recall leaves ncn_evt_cnt at 0, so the release
|
||||
* op won't see leftover events and requeue this callback forever.
|
||||
*/
|
||||
if (count > limit) {
|
||||
for (i = 0; i < count; ++i)
|
||||
nfsd_notify_event_put(events[i]);
|
||||
goto out_recall;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
nf = nfsd_file_get(rcu_dereference(dp->dl_stid.sc_file->fi_deleg_file));
|
||||
rcu_read_unlock();
|
||||
|
|
@ -3588,6 +3598,35 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
|
|||
put_event:
|
||||
nfsd_notify_event_put(nne);
|
||||
}
|
||||
if (!error && (dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS))) {
|
||||
u32 *maskp = (u32 *)xdr_reserve_space(&stream, sizeof(*maskp));
|
||||
u8 *p;
|
||||
|
||||
if (maskp)
|
||||
p = nfsd4_encode_dir_attr_change(&stream, dp, nf);
|
||||
else
|
||||
p = ERR_PTR(-ENOBUFS);
|
||||
|
||||
if (IS_ERR(p)) {
|
||||
/*
|
||||
* The client asked to be told about dir attr changes
|
||||
* but the change could not be encoded. RFC 8881
|
||||
* s10.9.4 requires the server to recall the delegation
|
||||
* rather than drop a requested notification, so fall
|
||||
* through to recall. A NULL return instead means there
|
||||
* were no attributes to report, so omit the event in
|
||||
* that case.
|
||||
*/
|
||||
error = true;
|
||||
} else if (p) {
|
||||
*maskp = BIT(NOTIFY4_CHANGE_DIR_ATTRS);
|
||||
ncn->ncn_nf[count].notify_mask.count = 1;
|
||||
ncn->ncn_nf[count].notify_mask.element = maskp;
|
||||
ncn->ncn_nf[count].notify_vals.data = p;
|
||||
ncn->ncn_nf[count].notify_vals.len = (u8 *)stream.p - p;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
if (!error) {
|
||||
ncn->ncn_nf_cnt = count;
|
||||
nfsd_file_put(nf);
|
||||
|
|
@ -3642,8 +3681,13 @@ nfsd4_cb_notify_release(struct nfsd4_callback *cb)
|
|||
struct nfs4_delegation *dp =
|
||||
container_of(ncn, struct nfs4_delegation, dl_cb_notify);
|
||||
|
||||
/* Drain events that arrived while this callback was in flight */
|
||||
if (READ_ONCE(ncn->ncn_evt_cnt) > 0)
|
||||
/*
|
||||
* Drain events that arrived while this callback was in flight, but
|
||||
* don't requeue against a revoked delegation: there's no point in
|
||||
* notifying a client that no longer holds it, and doing so can pin the
|
||||
* stid and spin the workqueue.
|
||||
*/
|
||||
if (!dp->dl_stid.sc_status && READ_ONCE(ncn->ncn_evt_cnt) > 0)
|
||||
nfsd4_run_cb_notify(ncn);
|
||||
nfs4_put_stid(&dp->dl_stid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4272,11 +4272,12 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
|
|||
struct dentry *dentry, struct nfs4_delegation *dp,
|
||||
struct nfsd_file *nf, char *name, u32 namelen)
|
||||
{
|
||||
struct path path = { .mnt = nf->nf_file->f_path.mnt,
|
||||
.dentry = dentry };
|
||||
struct path path = nf->nf_file->f_path;
|
||||
struct nfsd4_fattr_args args = { };
|
||||
const u32 *reqmask;
|
||||
uint32_t *attrmask;
|
||||
__be32 status;
|
||||
bool parent;
|
||||
int ret;
|
||||
|
||||
/* Reserve space for attrmask */
|
||||
|
|
@ -4288,9 +4289,29 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
|
|||
ne->ne_file.len = namelen;
|
||||
ne->ne_attrs.attrmask.element = attrmask;
|
||||
|
||||
/* FIXME: d_find_alias for inode ? */
|
||||
if (!path.dentry || !d_inode(path.dentry))
|
||||
goto noattrs;
|
||||
parent = (dentry == path.dentry);
|
||||
path.dentry = dentry;
|
||||
reqmask = parent ? dp->dl_dir_attrs : dp->dl_child_attrs;
|
||||
|
||||
/*
|
||||
* A NULL or negative dentry has no attributes to report (expected,
|
||||
* e.g. for the old entry of a rename or an entry already removed).
|
||||
* The client may also have been granted the notification while
|
||||
* requesting no attributes for this entry. Both cases encode an
|
||||
* empty attribute set rather than failing: the vfs_getattr() and
|
||||
* nfsd4_encode_attr_vals() failures below recall the delegation, so
|
||||
* a case with nothing to fetch must short-circuit ahead of them.
|
||||
*/
|
||||
if (!path.dentry || !d_inode(path.dentry) ||
|
||||
(!reqmask[0] && !reqmask[1])) {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is possible that the client was granted a delegation when a file
|
||||
|
|
@ -4299,37 +4320,34 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
|
|||
*/
|
||||
ret = vfs_getattr(&path, &args.stat, CB_NOTIFY_STATX_REQUEST_MASK, AT_STATX_SYNC_AS_STAT);
|
||||
if (ret)
|
||||
goto noattrs;
|
||||
return false;
|
||||
|
||||
args.change_attr = nfsd4_change_attribute(&args.stat);
|
||||
|
||||
attrmask[0] = dp->dl_child_attrs[0];
|
||||
attrmask[1] = dp->dl_child_attrs[1];
|
||||
if (parent) {
|
||||
attrmask[0] = dp->dl_dir_attrs[0];
|
||||
attrmask[1] = dp->dl_dir_attrs[1];
|
||||
} else {
|
||||
attrmask[0] = dp->dl_child_attrs[0];
|
||||
attrmask[1] = dp->dl_child_attrs[1];
|
||||
|
||||
if (!setup_notify_fhandle(dentry, dp, nf, &args))
|
||||
attrmask[0] &= ~FATTR4_WORD0_FILEHANDLE;
|
||||
|
||||
if (!(args.stat.result_mask & STATX_BTIME))
|
||||
attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
|
||||
}
|
||||
attrmask[2] = 0;
|
||||
|
||||
if (!setup_notify_fhandle(dentry, dp, nf, &args))
|
||||
attrmask[0] &= ~FATTR4_WORD0_FILEHANDLE;
|
||||
|
||||
if (!(args.stat.result_mask & STATX_BTIME))
|
||||
attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
|
||||
|
||||
ne->ne_attrs.attrmask.count = 2;
|
||||
ne->ne_attrs.attr_vals.data = (u8 *)xdr->p;
|
||||
|
||||
status = nfsd4_encode_attr_vals(xdr, attrmask, &args);
|
||||
if (status != nfs_ok)
|
||||
goto noattrs;
|
||||
return false;
|
||||
|
||||
ne->ne_attrs.attr_vals.len = (u8 *)xdr->p - ne->ne_attrs.attr_vals.data;
|
||||
return true;
|
||||
noattrs:
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4424,6 +4442,42 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* nfsd4_encode_dir_attr_change
|
||||
* @xdr: stream to which to encode the fattr4
|
||||
* @dp: delegation where the event occurred
|
||||
* @nf: nfsd_file opened on the directory
|
||||
*
|
||||
* Encode a dir attr change event.
|
||||
*
|
||||
* Return: a pointer to the start of the encoded event on success; NULL
|
||||
* if there were no requested attributes to report, in which case the
|
||||
* caller should omit the event; or an ERR_PTR if the event was requested
|
||||
* but could not be marshalled into @xdr, in which case the caller should
|
||||
* recall the delegation.
|
||||
*/
|
||||
u8 *nfsd4_encode_dir_attr_change(struct xdr_stream *xdr, struct nfs4_delegation *dp,
|
||||
struct nfsd_file *nf)
|
||||
{
|
||||
struct dentry *dentry = nf->nf_file->f_path.dentry;
|
||||
struct notify_attr4 na = { };
|
||||
u8 *p;
|
||||
|
||||
/* RFC 8881 s10.4.3: ne_file must be a zero-length string for dir attrs */
|
||||
if (!nfsd4_setup_notify_entry4(&na.na_changed_entry, xdr,
|
||||
dentry, dp, nf, "", 0))
|
||||
return ERR_PTR(-ENOBUFS);
|
||||
|
||||
/* No requested attributes to report; omit the event */
|
||||
if (!na.na_changed_entry.ne_attrs.attr_vals.len)
|
||||
return NULL;
|
||||
|
||||
p = (u8 *)xdr->p;
|
||||
if (!xdrgen_encode_notify_attr4(xdr, &na))
|
||||
return ERR_PTR(-ENOBUFS);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void svcxdr_init_encode_from_buffer(struct xdr_stream *xdr,
|
||||
struct xdr_buf *buf, __be32 *p, int bytes)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -669,7 +669,7 @@ xdrgen_decode_notify_add4(struct xdr_stream *xdr, struct notify_add4 *ptr)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool __maybe_unused
|
||||
bool
|
||||
xdrgen_decode_notify_attr4(struct xdr_stream *xdr, struct notify_attr4 *ptr)
|
||||
{
|
||||
if (!xdrgen_decode_notify_entry4(xdr, &ptr->na_changed_entry))
|
||||
|
|
@ -1091,7 +1091,7 @@ xdrgen_encode_notify_add4(struct xdr_stream *xdr, const struct notify_add4 *valu
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool __maybe_unused
|
||||
bool
|
||||
xdrgen_encode_notify_attr4(struct xdr_stream *xdr, const struct notify_attr4 *value)
|
||||
{
|
||||
if (!xdrgen_encode_notify_entry4(xdr, &value->na_changed_entry))
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ bool xdrgen_encode_notify_remove4(struct xdr_stream *xdr, const struct notify_re
|
|||
bool xdrgen_decode_notify_add4(struct xdr_stream *xdr, struct notify_add4 *ptr);
|
||||
bool xdrgen_encode_notify_add4(struct xdr_stream *xdr, const struct notify_add4 *value);
|
||||
|
||||
bool xdrgen_decode_notify_attr4(struct xdr_stream *xdr, struct notify_attr4 *ptr);
|
||||
bool xdrgen_encode_notify_attr4(struct xdr_stream *xdr, const struct notify_attr4 *value);
|
||||
|
||||
bool xdrgen_decode_notify_rename4(struct xdr_stream *xdr, struct notify_rename4 *ptr);
|
||||
bool xdrgen_encode_notify_rename4(struct xdr_stream *xdr, const struct notify_rename4 *value);
|
||||
|
||||
|
|
|
|||
|
|
@ -973,6 +973,8 @@ __be32 nfsd4_encode_fattr_to_buf(__be32 **p, int words,
|
|||
u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *nne,
|
||||
struct nfs4_delegation *dd, struct nfsd_file *nf,
|
||||
u32 *notify_mask);
|
||||
u8 *nfsd4_encode_dir_attr_change(struct xdr_stream *xdr, struct nfs4_delegation *dp,
|
||||
struct nfsd_file *nf);
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user