@@ -1122,6 +1122,7 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
};
struct rpc_clnt *client;
const struct cred *cred;
+ int ret;
if (clp->cl_minorversion == 0) {
if (!clp->cl_cred.cr_principal &&
@@ -1137,7 +1138,9 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
} else {
if (!conn->cb_xprt || !ses)
return -EINVAL;
- clp->cl_cb_session = ses;
+ if (!nfsd4_cb_get_session(ses))
+ return -EINVAL;
+ rcu_assign_pointer(clp->cl_cb_session, ses);
args.bc_xprt = conn->cb_xprt;
args.prognumber = clp->cl_cb_session->se_cb_prog;
args.protocol = conn->cb_xprt->xpt_class->xcl_ident |
@@ -1148,13 +1151,15 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
client = rpc_create(&args);
if (IS_ERR(client)) {
trace_nfsd_cb_setup_err(clp, PTR_ERR(client));
- return PTR_ERR(client);
+ ret = PTR_ERR(client);
+ goto out_put_ses;
}
cred = get_backchannel_cred(clp, client, ses);
if (!cred) {
trace_nfsd_cb_setup_err(clp, -ENOMEM);
rpc_shutdown_client(client);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto out_put_ses;
}
if (clp->cl_minorversion != 0)
@@ -1166,6 +1171,12 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
args.authflavor);
rcu_read_unlock();
return 0;
+out_put_ses:
+ if (clp->cl_minorversion != 0) {
+ rcu_assign_pointer(clp->cl_cb_session, NULL);
+ nfsd4_cb_put_session(ses);
+ }
+ return ret;
}
static void nfsd4_mark_cb_state(struct nfs4_client *clp, int newstate)
@@ -1529,11 +1540,15 @@ static void nfsd4_process_cb_update(struct nfsd4_callback *cb)
* kill the old client:
*/
if (clp->cl_cb_client) {
+ struct nfsd4_session *ses;
+
trace_nfsd_cb_bc_shutdown(clp, cb);
rpc_shutdown_client(clp->cl_cb_client);
clp->cl_cb_client = NULL;
put_cred(clp->cl_cb_cred);
clp->cl_cb_cred = NULL;
+ ses = rcu_replace_pointer(clp->cl_cb_session, NULL, true);
+ nfsd4_cb_put_session(ses);
}
if (clp->cl_cb_conn.cb_xprt) {
svc_xprt_put(clp->cl_cb_conn.cb_xprt);
@@ -2180,7 +2180,7 @@ static void __free_session(struct nfsd4_session *ses)
{
free_session_slots(ses, 0);
xa_destroy(&ses->se_slots);
- kfree(ses);
+ kfree_rcu(ses, se_rcu);
}
static void free_session(struct nfsd4_session *ses)
@@ -3283,7 +3283,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name,
clp->cl_time = ktime_get_boottime_seconds();
copy_verf(clp, verf);
memcpy(&clp->cl_addr, sa, sizeof(struct sockaddr_storage));
- clp->cl_cb_session = NULL;
+ rcu_assign_pointer(clp->cl_cb_session, NULL);
clp->net = net;
clp->cl_nfsd_dentry = nfsd_client_mkdir(
nn, &clp->cl_nfsdfs,
@@ -4251,6 +4251,13 @@ nfsd4_destroy_session(struct svc_rqst *r, struct nfsd4_compound_state *cstate,
status = nfserr_wrong_cred;
if (!nfsd4_mach_creds_match(ses->se_client, r))
goto out_put_session;
+
+ /*
+ * Is this session the backchannel session? Count an extra
+ * reference if so.
+ */
+ if (ses == rcu_access_pointer(ses->se_client->cl_cb_session))
+ ref_held_by_me++;
status = mark_session_dead_locked(ses, 1 + ref_held_by_me);
if (status)
goto out_put_session;
@@ -354,6 +354,7 @@ struct nfsd4_session {
u16 se_slot_gen;
bool se_dead;
u32 se_target_maxslots;
+ struct rcu_head se_rcu;
};
/* formatted contents of nfs4_sessionid */
@@ -465,7 +466,7 @@ struct nfs4_client {
#define NFSD4_CB_FAULT 3
int cl_cb_state;
struct nfsd4_callback cl_cb_null;
- struct nfsd4_session *cl_cb_session;
+ struct nfsd4_session __rcu *cl_cb_session;
/* for all client information that callback code might need: */
spinlock_t cl_lock;
Currently, this is just a pointer to the most recent session, but there is no guarantee that the session is still valid and in memory. It's possible for this pointer go NULL or replaced. First, embed a struct rcu in nfsd4_session and free it via free_rcu. Ensure that when clp->cl_cb_session pointer is changed, that it is done via RCU-safe methods. This will allow callbacks to access the cl_cb_session pointer safely via RCU. Signed-off-by: Jeff Layton <jlayton@kernel.org> --- fs/nfsd/nfs4callback.c | 21 ++++++++++++++++++--- fs/nfsd/nfs4state.c | 11 +++++++++-- fs/nfsd/state.h | 3 ++- 3 files changed, 29 insertions(+), 6 deletions(-)