Signed-off-by: Dai Ngo <dai.ngo@xxxxxxxxxx>
---
fs/nfsd/nfs4state.c | 446 +++++++++++++++++++++++++++++++++++++++++++++++++---
fs/nfsd/nfsd.h | 1 +
fs/nfsd/state.h | 6 +
3 files changed, 430 insertions(+), 23 deletions(-)
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 1956d377d1a6..a50a670e2088 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -1917,10 +1917,27 @@ find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net,
{
struct nfsd4_session *session;
__be32 status = nfserr_badsession;
+ struct nfs4_client *clp;
session = __find_in_sessionid_hashtbl(sessionid, net);
if (!session)
goto out;
+ clp = session->se_client;
+ if (clp) {
+ clear_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags);
+ /* need to sync with thread resolving lock/deleg conflict */
+ spin_lock(&clp->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags)) {
+ spin_unlock(&clp->cl_cs_lock);
+ session = NULL;
+ goto out;
+ }
+ if (test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags)) {
+ clear_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags);
+ set_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags);
+ }
+ spin_unlock(&clp->cl_cs_lock);
+ }
status = nfsd4_get_session_locked(session);
if (status)
session = NULL;
@@ -1990,6 +2007,7 @@ static struct nfs4_client *alloc_client(struct xdr_netobj name)
INIT_LIST_HEAD(&clp->cl_openowners);
INIT_LIST_HEAD(&clp->cl_delegations);
INIT_LIST_HEAD(&clp->cl_lru);
+ INIT_LIST_HEAD(&clp->cl_cs_list);
INIT_LIST_HEAD(&clp->cl_revoked);
#ifdef CONFIG_NFSD_PNFS
INIT_LIST_HEAD(&clp->cl_lo_states);
@@ -1997,6 +2015,7 @@ static struct nfs4_client *alloc_client(struct xdr_netobj name)
INIT_LIST_HEAD(&clp->async_copies);
spin_lock_init(&clp->async_lock);
spin_lock_init(&clp->cl_lock);
+ spin_lock_init(&clp->cl_cs_lock);
rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");
return clp;
err_no_hashtbl:
@@ -2394,6 +2413,10 @@ static int client_info_show(struct seq_file *m, void *v)
seq_puts(m, "status: confirmed\n");
else
seq_puts(m, "status: unconfirmed\n");
+ seq_printf(m, "courtesy client: %s\n",
+ test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags) ? "yes" : "no");
+ seq_printf(m, "seconds from last renew: %lld\n",
+ ktime_get_boottime_seconds() - clp->cl_time);
seq_printf(m, "name: ");
seq_quote_mem(m, clp->cl_name.data, clp->cl_name.len);
seq_printf(m, "\nminor version: %d\n", clp->cl_minorversion);
@@ -2814,8 +2837,22 @@ find_clp_in_name_tree(struct xdr_netobj *name, struct rb_root *root)
node = node->rb_left;
else if (cmp < 0)
node = node->rb_right;
- else
+ else {
+ clear_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags);
+ /* sync with thread resolving lock/deleg conflict */
+ spin_lock(&clp->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY,
+ &clp->cl_flags)) {
+ spin_unlock(&clp->cl_cs_lock);
+ return NULL;
+ }
+ if (test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags)) {
+ clear_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags);
+ set_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags);
+ }
+ spin_unlock(&clp->cl_cs_lock);
return clp;
+ }
}
return NULL;
}
@@ -2861,6 +2898,20 @@ find_client_in_id_table(struct list_head *tbl, clientid_t *clid, bool sessions)
if (same_clid(&clp->cl_clientid, clid)) {
if ((bool)clp->cl_minorversion != sessions)
return NULL;
+
+ /* need to sync with thread resolving lock/deleg conflict */
+ clear_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags);
+ spin_lock(&clp->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY,
+ &clp->cl_flags)) {
+ spin_unlock(&clp->cl_cs_lock);
+ continue;
+ }
+ if (test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags)) {
+ clear_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags);
+ set_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags);
+ }
+ spin_unlock(&clp->cl_cs_lock);
renew_client_locked(clp);
return clp;
}
@@ -3177,6 +3228,12 @@ nfsd4_exchange_id(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
/* Cases below refer to rfc 5661 section 18.35.4: */
spin_lock(&nn->client_lock);
conf = find_confirmed_client_by_name(&exid->clname, nn);
+ if (conf && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &conf->cl_flags)) {
+ spin_lock(&conf->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &conf->cl_flags);
+ spin_unlock(&conf->cl_cs_lock);
+ conf = NULL;
+ }
if (conf) {
bool creds_match = same_creds(&conf->cl_cred, &rqstp->rq_cred);
bool verfs_match = same_verf(&verf, &conf->cl_verifier);
@@ -3444,6 +3501,12 @@ nfsd4_create_session(struct svc_rqst *rqstp,
spin_lock(&nn->client_lock);
unconf = find_unconfirmed_client(&cr_ses->clientid, true, nn);
conf = find_confirmed_client(&cr_ses->clientid, true, nn);
+ if (conf && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &conf->cl_flags)) {
+ spin_lock(&conf->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &conf->cl_flags);
+ spin_unlock(&conf->cl_cs_lock);
+ conf = NULL;
+ }
WARN_ON_ONCE(conf && unconf);
if (conf) {
@@ -3475,6 +3538,12 @@ nfsd4_create_session(struct svc_rqst *rqstp,
goto out_free_conn;
}
old = find_confirmed_client_by_name(&unconf->cl_name, nn);
+ if (old && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &old->cl_flags)) {
+ spin_lock(&old->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &old->cl_flags);
+ spin_unlock(&old->cl_cs_lock);
+ old = NULL;
+ }
if (old) {
status = mark_client_expired_locked(old);
if (status) {
@@ -3613,6 +3682,7 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
struct nfsd4_session *session;
struct net *net = SVC_NET(rqstp);
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+ struct nfs4_client *clp;
if (!nfsd4_last_compound_op(rqstp))
return nfserr_not_only_op;
@@ -3645,6 +3715,16 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
nfsd4_init_conn(rqstp, conn, session);
status = nfs_ok;
out:
+ clp = session->se_client;
+ if (test_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags)) {
+ if (status == nfs_ok)
+ nfsd4_client_record_create(clp);
+ else {
+ spin_lock(&clp->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags);
+ spin_unlock(&clp->cl_cs_lock);
+ }
+ }
nfsd4_put_session(session);
out_no_session:
return status;
@@ -3667,6 +3747,7 @@ nfsd4_destroy_session(struct svc_rqst *r, struct nfsd4_compound_state *cstate,
int ref_held_by_me = 0;
struct net *net = SVC_NET(r);
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+ struct nfs4_client *clp;
status = nfserr_not_only_op;
if (nfsd4_compound_in_session(cstate, sessionid)) {
@@ -3679,6 +3760,15 @@ nfsd4_destroy_session(struct svc_rqst *r, struct nfsd4_compound_state *cstate,
ses = find_in_sessionid_hashtbl(sessionid, net, &status);
if (!ses)
goto out_client_lock;
+ clp = ses->se_client;
+ if (test_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags)) {
+ status = nfserr_badsession;
+ spin_lock(&clp->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags);
+ spin_unlock(&clp->cl_cs_lock);
+ goto out_put_session;
+ }
+
status = nfserr_wrong_cred;
if (!nfsd4_mach_creds_match(ses->se_client, r))
goto out_put_session;
@@ -3783,7 +3873,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_compoundres *resp = rqstp->rq_resp;
struct xdr_stream *xdr = resp->xdr;
struct nfsd4_session *session;
- struct nfs4_client *clp;
+ struct nfs4_client *clp = NULL;
struct nfsd4_slot *slot;
struct nfsd4_conn *conn;
__be32 status;
@@ -3893,6 +3983,15 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (conn)
free_conn(conn);
spin_unlock(&nn->client_lock);
+ if (clp && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &clp->cl_flags)) {
+ if (status == nfs_ok)
+ nfsd4_client_record_create(clp);
+ else {
+ spin_lock(&clp->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags);
+ spin_unlock(&clp->cl_cs_lock);
+ }
+ }
return status;
out_put_session:
nfsd4_put_session_locked(session);
@@ -3929,6 +4028,12 @@ nfsd4_destroy_clientid(struct svc_rqst *rqstp,
spin_lock(&nn->client_lock);
unconf = find_unconfirmed_client(&dc->clientid, true, nn);
conf = find_confirmed_client(&dc->clientid, true, nn);
+ if (conf && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &conf->cl_flags)) {
+ spin_lock(&conf->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &conf->cl_flags);
+ spin_unlock(&conf->cl_cs_lock);
+ conf = NULL;
+ }
WARN_ON_ONCE(conf && unconf);
if (conf) {
@@ -4012,12 +4117,17 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfs4_client *unconf = NULL;
__be32 status;
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+ struct nfs4_client *cclient = NULL;
new = create_client(clname, rqstp, &clverifier);
if (new == NULL)
return nfserr_jukebox;
spin_lock(&nn->client_lock);
conf = find_confirmed_client_by_name(&clname, nn);
+ if (conf && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &conf->cl_flags)) {
+ cclient = conf;
+ conf = NULL;
+ }
if (conf && client_has_state(conf)) {
status = nfserr_clid_inuse;
if (clp_used_exchangeid(conf))
@@ -4048,7 +4158,11 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
new = NULL;
status = nfs_ok;
out:
+ if (cclient)
+ unhash_client_locked(cclient);
spin_unlock(&nn->client_lock);
+ if (cclient)
+ expire_client(cclient);
if (new)
free_client(new);
if (unconf) {
@@ -4078,6 +4192,12 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
spin_lock(&nn->client_lock);
conf = find_confirmed_client(clid, false, nn);
unconf = find_unconfirmed_client(clid, false, nn);
+ if (conf && test_bit(NFSD4_CLIENT_COURTESY_CLNT, &conf->cl_flags)) {
+ spin_lock(&conf->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &conf->cl_flags);
+ spin_unlock(&conf->cl_cs_lock);
+ conf = NULL;
+ }
/*
* We try hard to give out unique clientid's, so if we get an
* attempt to confirm the same clientid with a different cred,
@@ -4108,6 +4228,13 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
nfsd4_change_callback(conf, &unconf->cl_cb_conn);
} else {
old = find_confirmed_client_by_name(&unconf->cl_name, nn);
+ if (old && test_bit(NFSD4_CLIENT_COURTESY_CLNT,
+ &old->cl_flags)) {
+ spin_lock(&old->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &old->cl_flags);
+ spin_unlock(&old->cl_cs_lock);
+ old = NULL;
+ }
if (old) {
status = nfserr_clid_inuse;
if (client_has_state(old)
@@ -4691,18 +4818,41 @@ nfsd_break_deleg_cb(struct file_lock *fl)
return ret;
}
+/*
+ * Function returns true if lease conflict was resolved
+ * else returns false.
+ */
static bool nfsd_breaker_owns_lease(struct file_lock *fl)
{
struct nfs4_delegation *dl = fl->fl_owner;
struct svc_rqst *rqst;
struct nfs4_client *clp;
+ clp = dl->dl_stid.sc_client;
+
+ /*
+ * need to sync with courtesy client trying to reconnect using
+ * the cl_cs_lock, nn->client_lock can not be used since this
+ * function is called with the fl_lck held.
+ */
+ spin_lock(&clp->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags)) {
+ spin_unlock(&clp->cl_cs_lock);
+ return true;
+ }
+ if (test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags)) {
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags);
+ spin_unlock(&clp->cl_cs_lock);
+ return true;
+ }
+ spin_unlock(&clp->cl_cs_lock);
+
if (!i_am_nfsd())
- return NULL;
+ return false;
rqst = kthread_data(current);
/* Note rq_prog == NFS_ACL_PROGRAM is also possible: */
if (rqst->rq_prog != NFS_PROGRAM || rqst->rq_vers < 4)
- return NULL;
+ return false;
clp = *(rqst->rq_lease_breaker);
return dl->dl_stid.sc_client == clp;
}
@@ -4735,7 +4885,7 @@ static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4
}
static struct nfs4_client *lookup_clientid(clientid_t *clid, bool sessions,
- struct nfsd_net *nn)
+ struct nfsd_net *nn)
{
struct nfs4_client *found;
@@ -4765,6 +4915,9 @@ static __be32 set_client(clientid_t *clid,
cstate->clp = lookup_clientid(clid, false, nn);
if (!cstate->clp)
return nfserr_expired;
+
+ if (test_bit(NFSD4_CLIENT_COURTESY_CLNT, &cstate->clp->cl_flags))
+ nfsd4_client_record_create(cstate->clp);
return nfs_ok;
}
@@ -4917,9 +5070,89 @@ nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
return nfsd_setattr(rqstp, fh, &iattr, 0, (time64_t)0);
}
-static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
+static bool
+nfs4_check_access_deny_bmap(struct nfs4_ol_stateid *stp, u32 access,
+ bool share_access)
+{
+ if (share_access) {
+ if (!stp->st_deny_bmap)
+ return false;
+
+ if ((stp->st_deny_bmap & (1 << NFS4_SHARE_DENY_BOTH)) ||
+ (access & NFS4_SHARE_ACCESS_READ &&
+ stp->st_deny_bmap & (1 << NFS4_SHARE_DENY_READ)) ||
+ (access & NFS4_SHARE_ACCESS_WRITE &&
+ stp->st_deny_bmap & (1 << NFS4_SHARE_DENY_WRITE))) {
+ return true;
+ }
+ return false;
+ }
+ if ((access & NFS4_SHARE_DENY_BOTH) ||
+ (access & NFS4_SHARE_DENY_READ &&
+ stp->st_access_bmap & (1 << NFS4_SHARE_ACCESS_READ)) ||
+ (access & NFS4_SHARE_DENY_WRITE &&
+ stp->st_access_bmap & (1 << NFS4_SHARE_ACCESS_WRITE))) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * This function is called to check whether nfserr_share_denied should
+ * be returning to client.
+ *
+ * access: is op_share_access if share_access is true.
+ * Check if access mode, op_share_access, would conflict with
+ * the current deny mode of the file 'fp'.
+ * access: is op_share_deny if share_access is false.
+ * Check if the deny mode, op_share_deny, would conflict with
+ * current access of the file 'fp'.
+ * stp: skip checking this entry.
+ * new_stp: normal open, not open upgrade.
+ *
+ * Function returns:
+ * true - access/deny mode conflict with normal client.
+ * false - no conflict or conflict with courtesy client(s) is resolved.
+ */
+static bool
+nfs4_conflict_clients(struct nfs4_file *fp, bool new_stp,
+ struct nfs4_ol_stateid *stp, u32 access, bool share_access)
+{
+ struct nfs4_ol_stateid *st;
+ struct nfs4_client *cl;
+ bool conflict = false;
+
+ lockdep_assert_held(&fp->fi_lock);
+ list_for_each_entry(st, &fp->fi_stateids, st_perfile) {
+ if (st->st_openstp || (st == stp && new_stp) ||
+ (!nfs4_check_access_deny_bmap(st,
+ access, share_access)))
+ continue;
+
+ /* need to sync with courtesy client trying to reconnect */
+ cl = st->st_stid.sc_client;
+ spin_lock(&cl->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY, &cl->cl_flags)) {
+ spin_unlock(&cl->cl_cs_lock);
+ continue;
+ }
+ if (test_bit(NFSD4_CLIENT_COURTESY, &cl->cl_flags)) {
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &cl->cl_flags);
+ spin_unlock(&cl->cl_cs_lock);
+ continue;
+ }
+ /* conflict not caused by courtesy client */
+ spin_unlock(&cl->cl_cs_lock);
+ conflict = true;
+ break;
+ }
+ return conflict;
+}
+
+static __be32
+nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp,
- struct nfsd4_open *open)
+ struct nfsd4_open *open, bool new_stp)
{
struct nfsd_file *nf = NULL;
__be32 status;
@@ -4935,15 +5168,29 @@ static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
*/
status = nfs4_file_check_deny(fp, open->op_share_deny);
if (status != nfs_ok) {
- spin_unlock(&fp->fi_lock);
- goto out;
+ if (status != nfserr_share_denied) {
+ spin_unlock(&fp->fi_lock);
+ goto out;
+ }
+ if (nfs4_conflict_clients(fp, new_stp, stp,
+ open->op_share_deny, false)) {
+ spin_unlock(&fp->fi_lock);
+ goto out;
+ }
}
/* set access to the file */
status = nfs4_file_get_access(fp, open->op_share_access);
if (status != nfs_ok) {
- spin_unlock(&fp->fi_lock);
- goto out;
+ if (status != nfserr_share_denied) {
+ spin_unlock(&fp->fi_lock);
+ goto out;
+ }
+ if (nfs4_conflict_clients(fp, new_stp, stp,
+ open->op_share_access, true)) {
+ spin_unlock(&fp->fi_lock);
+ goto out;
+ }
}
/* Set access bits in stateid */
@@ -4994,7 +5241,7 @@ nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *c
unsigned char old_deny_bmap = stp->st_deny_bmap;
if (!test_access(open->op_share_access, stp))
- return nfs4_get_vfs_file(rqstp, fp, cur_fh, stp, open);
+ return nfs4_get_vfs_file(rqstp, fp, cur_fh, stp, open, false);
/* test and set deny mode */
spin_lock(&fp->fi_lock);
@@ -5343,7 +5590,7 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
goto out;
}
} else {
- status = nfs4_get_vfs_file(rqstp, fp, current_fh, stp, open);
+ status = nfs4_get_vfs_file(rqstp, fp, current_fh, stp, open, true);
if (status) {
stp->st_stid.sc_type = NFS4_CLOSED_STID;
release_open_stateid(stp);
@@ -5577,6 +5824,121 @@ static void nfsd4_ssc_expire_umount(struct nfsd_net *nn)
}
#endif
+static bool
+nfs4_anylock_blocker(struct nfs4_client *clp)
+{
+ int i;
+ struct nfs4_stateowner *so, *tmp;
+ struct nfs4_lockowner *lo;
+ struct nfs4_ol_stateid *stp;
+ struct nfs4_file *nf;
+ struct inode *ino;
+ struct file_lock_context *ctx;
+ struct file_lock *fl;
+
+ spin_lock(&clp->cl_lock);
+ for (i = 0; i < OWNER_HASH_SIZE; i++) {
+ /* scan each lock owner */
+ list_for_each_entry_safe(so, tmp, &clp->cl_ownerstr_hashtbl[i],
+ so_strhash) {
+ if (so->so_is_open_owner)
+ continue;
+
+ /* scan lock states of this lock owner */
+ lo = lockowner(so);
+ list_for_each_entry(stp, &lo->lo_owner.so_stateids,
+ st_perstateowner) {
+ nf = stp->st_stid.sc_file;
+ ino = nf->fi_inode;
+ ctx = ino->i_flctx;
+ if (!ctx)
+ continue;
+ /* check each lock belongs to this lock state */
+ list_for_each_entry(fl, &ctx->flc_posix, fl_list) {
+ if (fl->fl_owner != lo)
+ continue;
+ if (!list_empty(&fl->fl_blocked_requests)) {
+ spin_unlock(&clp->cl_lock);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ spin_unlock(&clp->cl_lock);
+ return false;
+}
+
+static void
+nfs4_get_client_reaplist(struct nfsd_net *nn, struct list_head *reaplist,
+ struct laundry_time *lt)
+{
+ struct list_head *pos, *next;
+ struct nfs4_client *clp;
+ bool cour;
+ struct list_head cslist;
+
+ INIT_LIST_HEAD(reaplist);
+ INIT_LIST_HEAD(&cslist);
+ spin_lock(&nn->client_lock);
+ list_for_each_safe(pos, next, &nn->client_lru) {
+ clp = list_entry(pos, struct nfs4_client, cl_lru);
+ if (!state_expired(lt, clp->cl_time))
+ break;
+
+ /* client expired */
+ if (!client_has_state(clp)) {
+ if (mark_client_expired_locked(clp))
+ continue;
+ list_add(&clp->cl_lru, reaplist);
+ continue;
+ }
+
+ /* expired client has state */
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags))
+ goto exp_client;
+
+ cour = test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags);
+ if (cour &&
+ ktime_get_boottime_seconds() >= clp->courtesy_client_expiry)
+ goto exp_client;
+
+ if (nfs4_anylock_blocker(clp)) {
+ /* expired client has state and has blocker. */
+exp_client:
+ if (mark_client_expired_locked(clp))
+ continue;
+ list_add(&clp->cl_lru, reaplist);
+ continue;
+ }
+ /*
+ * Client expired and has state and has no blockers.
+ * If there is race condition with blockers, next time
+ * the laundromat runs it will catch it and expires
+ * the client.
+ */
+ if (!cour) {
+ set_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags);
+ clp->courtesy_client_expiry = ktime_get_boottime_seconds() +
+ NFSD_COURTESY_CLIENT_TIMEOUT;
+ list_add(&clp->cl_cs_list, &cslist);
+ }
+ }
+ spin_unlock(&nn->client_lock);
+
+ list_for_each_entry(clp, &cslist, cl_cs_list) {
+ spin_lock(&clp->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags) ||
+ !test_bit(NFSD4_CLIENT_COURTESY,
+ &clp->cl_flags)) {
+ spin_unlock(&clp->cl_cs_lock);
+ continue;
+ }
+ spin_unlock(&clp->cl_cs_lock);
+ nfsd4_client_record_remove(clp);
+ }
+}
+
static time64_t
nfs4_laundromat(struct nfsd_net *nn)
{
@@ -5610,16 +5972,7 @@ nfs4_laundromat(struct nfsd_net *nn)
}
spin_unlock(&nn->s2s_cp_lock);
- spin_lock(&nn->client_lock);
- list_for_each_safe(pos, next, &nn->client_lru) {
- clp = list_entry(pos, struct nfs4_client, cl_lru);
- if (!state_expired(<, clp->cl_time))
- break;
- if (mark_client_expired_locked(clp))
- continue;
- list_add(&clp->cl_lru, &reaplist);
- }
- spin_unlock(&nn->client_lock);
+ nfs4_get_client_reaplist(nn, &reaplist, <);
list_for_each_safe(pos, next, &reaplist) {
clp = list_entry(pos, struct nfs4_client, cl_lru);
trace_nfsd_clid_purged(&clp->cl_clientid);
@@ -6001,6 +6354,15 @@ static __be32 find_cpntf_state(struct nfsd_net *nn, stateid_t *st,
found = lookup_clientid(&cps->cp_p_clid, true, nn);
if (!found)
goto out;
+ if (test_bit(NFSD4_CLIENT_COURTESY_CLNT, &found->cl_flags)) {
+ spin_lock(&found->cl_cs_lock);
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &found->cl_flags);
+ spin_unlock(&found->cl_cs_lock);
+ if (atomic_dec_and_lock(&found->cl_rpc_users,
+ &nn->client_lock))
+ spin_unlock(&nn->client_lock);
+ goto out;
+ }
*stid = find_stateid_by_type(found, &cps->cp_p_stateid,
NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID);
@@ -6501,6 +6863,43 @@ nfs4_transform_lock_offset(struct file_lock *lock)
lock->fl_end = OFFSET_MAX;
}
+/**
+ * nfsd4_fl_lock_conflict - check if lock conflict can be resolved.
+ *
+ * @fl: pointer to file_lock with a potential conflict
+ * Return values:
+ * %true: real conflict, lock conflict can not be resolved.
+ * %false: no conflict, lock conflict was resolved.
+ *
+ * Note that this function is called while the flc_lock is held.
+ */
+static bool
+nfsd4_fl_lock_conflict(struct file_lock *fl)
+{
+ struct nfs4_lockowner *lo;
+ struct nfs4_client *clp;
+ bool rc = true;
+
+ if (!fl)
+ return true;
+ lo = (struct nfs4_lockowner *)fl->fl_owner;
+ clp = lo->lo_owner.so_client;
+
+ /* need to sync with courtesy client trying to reconnect */
+ spin_lock(&clp->cl_cs_lock);
+ if (test_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags))
+ rc = false;
+ else {
+ if (test_bit(NFSD4_CLIENT_COURTESY, &clp->cl_flags)) {
+ set_bit(NFSD4_CLIENT_DESTROY_COURTESY, &clp->cl_flags);
+ rc = false;
+ } else
+ rc = true;
+ }
+ spin_unlock(&clp->cl_cs_lock);
+ return rc;
+}
+
static fl_owner_t
nfsd4_fl_get_owner(fl_owner_t owner)
{
@@ -6548,6 +6947,7 @@ static const struct lock_manager_operations nfsd_posix_mng_ops = {
.lm_notify = nfsd4_lm_notify,
.lm_get_owner = nfsd4_fl_get_owner,
.lm_put_owner = nfsd4_fl_put_owner,
+ .lm_lock_conflict = nfsd4_fl_lock_conflict,
};
static inline void
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index 498e5a489826..5e3e699c8e76 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -336,6 +336,7 @@ void nfsd_lockd_shutdown(void);
#define COMPOUND_ERR_SLACK_SPACE 16 /* OP_SETATTR */
#define NFSD_LAUNDROMAT_MINTIMEOUT 1 /* seconds */
+#define NFSD_COURTESY_CLIENT_TIMEOUT (24 * 60 * 60) /* seconds */
/*
* The following attributes are currently not supported by the NFSv4 server:
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index e73bdbb1634a..c3c65421d422 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -345,6 +345,9 @@ struct nfs4_client {
#define NFSD4_CLIENT_UPCALL_LOCK (5) /* upcall serialization */
#define NFSD4_CLIENT_CB_FLAG_MASK (1 << NFSD4_CLIENT_CB_UPDATE | \
1 << NFSD4_CLIENT_CB_KILL)
+#define NFSD4_CLIENT_COURTESY (6) /* be nice to expired client */
+#define NFSD4_CLIENT_DESTROY_COURTESY (7)
+#define NFSD4_CLIENT_COURTESY_CLNT (8)
unsigned long cl_flags;
const struct cred *cl_cb_cred;
struct rpc_clnt *cl_cb_client;
@@ -385,6 +388,9 @@ struct nfs4_client {
struct list_head async_copies; /* list of async copies */
spinlock_t async_lock; /* lock for async copies */
atomic_t cl_cb_inflight; /* Outstanding callbacks */
+ int courtesy_client_expiry;
+ spinlock_t cl_cs_lock;
+ struct list_head cl_cs_list;
};
/* struct nfs4_client_reset
--
2.9.5