Commit 1d3dd1d5 authored by Dai Ngo's avatar Dai Ngo Committed by Chuck Lever

NFSD: Enable write delegation support

This patch grants write delegations for OPEN with NFS4_SHARE_ACCESS_WRITE
if there is no conflict with other OPENs.

Write delegation conflicts with another OPEN, REMOVE, RENAME and SETATTR
are handled the same as read delegation using notify_change,
try_break_deleg.

The NFSv4.0 protocol does not enable a server to determine that a
conflicting GETATTR originated from the client holding the
delegation versus coming from some other client. With NFSv4.1 and
later, the SEQUENCE operation that begins each COMPOUND contains a
client ID, so delegation recall can be safely squelched in this case.

With NFSv4.0, however, the server must recall or send a CB_GETATTR
(per RFC 7530 Section 16.7.5) even when the GETATTR originates from
the client holding that delegation.

An NFSv4.0 client can trigger a pathological situation if it always
sends a DELEGRETURN preceded by a conflicting GETATTR in the same
COMPOUND. COMPOUND execution will always stop at the GETATTR and the
DELEGRETURN will never get executed. The server eventually revokes
the delegation, which can result in loss of open or lock state.

Tracepoint added to track whether read or write delegation is granted.
Signed-off-by: default avatarDai Ngo <dai.ngo@oracle.com>
Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
parent 50bce06f
......@@ -649,6 +649,18 @@ find_readable_file(struct nfs4_file *f)
return ret;
}
static struct nfsd_file *
find_rw_file(struct nfs4_file *f)
{
struct nfsd_file *ret;
spin_lock(&f->fi_lock);
ret = nfsd_file_get(f->fi_fds[O_RDWR]);
spin_unlock(&f->fi_lock);
return ret;
}
struct nfsd_file *
find_any_file(struct nfs4_file *f)
{
......@@ -1144,7 +1156,7 @@ static void block_delegations(struct knfsd_fh *fh)
static struct nfs4_delegation *
alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
struct nfs4_clnt_odstate *odstate)
struct nfs4_clnt_odstate *odstate, u32 dl_type)
{
struct nfs4_delegation *dp;
long n;
......@@ -1170,7 +1182,7 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
INIT_LIST_HEAD(&dp->dl_recall_lru);
dp->dl_clnt_odstate = odstate;
get_clnt_odstate(odstate);
dp->dl_type = NFS4_OPEN_DELEGATE_READ;
dp->dl_type = dl_type;
dp->dl_retries = 1;
dp->dl_recalled = false;
nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
......@@ -5449,8 +5461,9 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
struct nfs4_file *fp = stp->st_stid.sc_file;
struct nfs4_clnt_odstate *odstate = stp->st_clnt_odstate;
struct nfs4_delegation *dp;
struct nfsd_file *nf;
struct nfsd_file *nf = NULL;
struct file_lock *fl;
u32 dl_type;
/*
* The fi_had_conflict and nfs_get_existing_delegation checks
......@@ -5460,15 +5473,35 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
if (fp->fi_had_conflict)
return ERR_PTR(-EAGAIN);
nf = find_readable_file(fp);
if (!nf) {
/*
* We probably could attempt another open and get a read
* delegation, but for now, don't bother until the
* client actually sends us one.
*/
return ERR_PTR(-EAGAIN);
/*
* Try for a write delegation first. RFC8881 section 10.4 says:
*
* "An OPEN_DELEGATE_WRITE delegation allows the client to handle,
* on its own, all opens."
*
* Furthermore the client can use a write delegation for most READ
* operations as well, so we require a O_RDWR file here.
*
* Offer a write delegation in the case of a BOTH open, and ensure
* we get the O_RDWR descriptor.
*/
if ((open->op_share_access & NFS4_SHARE_ACCESS_BOTH) == NFS4_SHARE_ACCESS_BOTH) {
nf = find_rw_file(fp);
dl_type = NFS4_OPEN_DELEGATE_WRITE;
}
/*
* If the file is being opened O_RDONLY or we couldn't get a O_RDWR
* file for some reason, then try for a read delegation instead.
*/
if (!nf && (open->op_share_access & NFS4_SHARE_ACCESS_READ)) {
nf = find_readable_file(fp);
dl_type = NFS4_OPEN_DELEGATE_READ;
}
if (!nf)
return ERR_PTR(-EAGAIN);
spin_lock(&state_lock);
spin_lock(&fp->fi_lock);
if (nfs4_delegation_exists(clp, fp))
......@@ -5491,11 +5524,11 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
return ERR_PTR(status);
status = -ENOMEM;
dp = alloc_init_deleg(clp, fp, odstate);
dp = alloc_init_deleg(clp, fp, odstate, dl_type);
if (!dp)
goto out_delegees;
fl = nfs4_alloc_init_lease(dp, NFS4_OPEN_DELEGATE_READ);
fl = nfs4_alloc_init_lease(dp, dl_type);
if (!fl)
goto out_clnt_odstate;
......@@ -5568,10 +5601,28 @@ static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status)
}
/*
* Attempt to hand out a delegation.
* The Linux NFS server does not offer write delegations to NFSv4.0
* clients in order to avoid conflicts between write delegations and
* GETATTRs requesting CHANGE or SIZE attributes.
*
* With NFSv4.1 and later minorversions, the SEQUENCE operation that
* begins each COMPOUND contains a client ID. Delegation recall can
* be avoided when the server recognizes the client sending a
* GETATTR also holds write delegation it conflicts with.
*
* However, the NFSv4.0 protocol does not enable a server to
* determine that a GETATTR originated from the client holding the
* conflicting delegation versus coming from some other client. Per
* RFC 7530 Section 16.7.5, the server must recall or send a
* CB_GETATTR even when the GETATTR originates from the client that
* holds the conflicting delegation.
*
* Note we don't support write delegations, and won't until the vfs has
* proper support for them.
* An NFSv4.0 client can trigger a pathological situation if it
* always sends a DELEGRETURN preceded by a conflicting GETATTR in
* the same COMPOUND. COMPOUND execution will always stop at the
* GETATTR and the DELEGRETURN will never get executed. The server
* eventually revokes the delegation, which can result in loss of
* open or lock state.
*/
static void
nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
......@@ -5590,8 +5641,6 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
case NFS4_OPEN_CLAIM_PREVIOUS:
if (!cb_up)
open->op_recall = 1;
if (open->op_delegate_type != NFS4_OPEN_DELEGATE_READ)
goto out_no_deleg;
break;
case NFS4_OPEN_CLAIM_NULL:
parent = currentfh;
......@@ -5606,6 +5655,9 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
goto out_no_deleg;
if (!cb_up || !(oo->oo_flags & NFS4_OO_CONFIRMED))
goto out_no_deleg;
if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE &&
!clp->cl_minorversion)
goto out_no_deleg;
break;
default:
goto out_no_deleg;
......@@ -5616,8 +5668,13 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));
trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) {
open->op_delegate_type = NFS4_OPEN_DELEGATE_WRITE;
trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid);
} else {
open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
}
nfs4_put_stid(&dp->dl_stid);
return;
out_no_deleg:
......
......@@ -607,6 +607,7 @@ DEFINE_STATEID_EVENT(layout_recall_release);
DEFINE_STATEID_EVENT(open);
DEFINE_STATEID_EVENT(deleg_read);
DEFINE_STATEID_EVENT(deleg_write);
DEFINE_STATEID_EVENT(deleg_return);
DEFINE_STATEID_EVENT(deleg_recall);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment