nfsd: allow forced expiration of NFSv4 clients

NFSv4 clients are automatically expired and all their locks removed if
they don't contact the server for a certain amount of time (the lease
period, 90 seconds by default).

There can still be situations where that's not enough, so allow
userspace to force expiry by writing "expire\n" to the new
nfsd/client/#/ctl file.

(The generic "ctl" name is because I expect we may want to allow other
operations on clients in the future.)

The write will not return until the client is expired and all of its
locks and other state removed.

The fault injection code also provides a way of expiring clients, but it
fails if there are any in-progress RPC's referencing the client.  Also,
its method of selecting a client to expire is a little more
primitive--it uses an IP address, which can't always uniquely specify an
NFSv4 client.

Signed-off-by: J. Bruce Fields <bfields@redhat.com>
This commit is contained in:
J. Bruce Fields 2019-06-19 12:43:11 -04:00
parent a204f25e37
commit 89c905becc

View File

@ -100,6 +100,13 @@ enum nfsd4_st_mutex_lock_subclass {
*/ */
static DECLARE_WAIT_QUEUE_HEAD(close_wq); static DECLARE_WAIT_QUEUE_HEAD(close_wq);
/*
* A waitqueue where a writer to clients/#/ctl destroying a client can
* wait for cl_rpc_users to drop to 0 and then for the client to be
* unhashed.
*/
static DECLARE_WAIT_QUEUE_HEAD(expiry_wq);
static struct kmem_cache *client_slab; static struct kmem_cache *client_slab;
static struct kmem_cache *openowner_slab; static struct kmem_cache *openowner_slab;
static struct kmem_cache *lockowner_slab; static struct kmem_cache *lockowner_slab;
@ -175,6 +182,8 @@ static void put_client_renew_locked(struct nfs4_client *clp)
return; return;
if (!is_client_expired(clp)) if (!is_client_expired(clp))
renew_client_locked(clp); renew_client_locked(clp);
else
wake_up_all(&expiry_wq);
} }
static void put_client_renew(struct nfs4_client *clp) static void put_client_renew(struct nfs4_client *clp)
@ -185,6 +194,8 @@ static void put_client_renew(struct nfs4_client *clp)
return; return;
if (!is_client_expired(clp)) if (!is_client_expired(clp))
renew_client_locked(clp); renew_client_locked(clp);
else
wake_up_all(&expiry_wq);
spin_unlock(&nn->client_lock); spin_unlock(&nn->client_lock);
} }
@ -1910,8 +1921,11 @@ free_client(struct nfs4_client *clp)
free_session(ses); free_session(ses);
} }
rpc_destroy_wait_queue(&clp->cl_cb_waitq); rpc_destroy_wait_queue(&clp->cl_cb_waitq);
if (clp->cl_nfsd_dentry) if (clp->cl_nfsd_dentry) {
nfsd_client_rmdir(clp->cl_nfsd_dentry); nfsd_client_rmdir(clp->cl_nfsd_dentry);
clp->cl_nfsd_dentry = NULL;
wake_up_all(&expiry_wq);
}
drop_client(clp); drop_client(clp);
} }
@ -2006,6 +2020,7 @@ __destroy_client(struct nfs4_client *clp)
if (clp->cl_cb_conn.cb_xprt) if (clp->cl_cb_conn.cb_xprt)
svc_xprt_put(clp->cl_cb_conn.cb_xprt); svc_xprt_put(clp->cl_cb_conn.cb_xprt);
free_client(clp); free_client(clp);
wake_up_all(&expiry_wq);
} }
static void static void
@ -2484,9 +2499,62 @@ static const struct file_operations client_states_fops = {
.release = client_opens_release, .release = client_opens_release,
}; };
/*
* Normally we refuse to destroy clients that are in use, but here the
* administrator is telling us to just do it. We also want to wait
* so the caller has a guarantee that the client's locks are gone by
* the time the write returns:
*/
void force_expire_client(struct nfs4_client *clp)
{
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
bool already_expired;
spin_lock(&clp->cl_lock);
clp->cl_time = 0;
spin_unlock(&clp->cl_lock);
wait_event(expiry_wq, atomic_read(&clp->cl_rpc_users) == 0);
spin_lock(&nn->client_lock);
already_expired = list_empty(&clp->cl_lru);
if (!already_expired)
unhash_client_locked(clp);
spin_unlock(&nn->client_lock);
if (!already_expired)
expire_client(clp);
else
wait_event(expiry_wq, clp->cl_nfsd_dentry == NULL);
}
static ssize_t client_ctl_write(struct file *file, const char __user *buf,
size_t size, loff_t *pos)
{
char *data;
struct nfs4_client *clp;
data = simple_transaction_get(file, buf, size);
if (IS_ERR(data))
return PTR_ERR(data);
if (size != 7 || 0 != memcmp(data, "expire\n", 7))
return -EINVAL;
clp = get_nfsdfs_clp(file_inode(file));
if (!clp)
return -ENXIO;
force_expire_client(clp);
drop_client(clp);
return 7;
}
static const struct file_operations client_ctl_fops = {
.write = client_ctl_write,
.release = simple_transaction_release,
};
static const struct tree_descr client_files[] = { static const struct tree_descr client_files[] = {
[0] = {"info", &client_info_fops, S_IRUSR}, [0] = {"info", &client_info_fops, S_IRUSR},
[1] = {"states", &client_states_fops, S_IRUSR}, [1] = {"states", &client_states_fops, S_IRUSR},
[2] = {"ctl", &client_ctl_fops, S_IRUSR|S_IWUSR},
[3] = {""}, [3] = {""},
}; };