mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
nfsd: dedup nfs4_client_to_reclaim inserts
nfs4_client_to_reclaim() unconditionally allocates a new
nfs4_client_reclaim, prepends it to reclaim_str_hashtbl[], and bumps
reclaim_str_hashtbl_size with no check for an existing entry for the
same client name. After a reboot with a populated recovery directory
that inflates the counter by one for every client that reclaims:
boot: load_recdir()
nfs4_client_to_reclaim(name) /* entry #1, size++ */
grace: RECLAIM_COMPLETE
__nfsd4_create_reclaim_record_grace()
nfs4_client_to_reclaim(name) /* entry #2, size++ */
inc_reclaim_complete() ends the grace period early only when
atomic_inc_return(&nn->nr_reclaim_complete) ==
nn->reclaim_str_hashtbl_size
With reclaim_str_hashtbl_size at 2N and nr_reclaim_complete capped at
N, the equality never holds and the fast end-of-grace path is dead.
The grace period always runs out the full 90-second laundromat timer,
and the shadow entry left in the hash table carries a dangling cr_clp
for any reader that walks it.
Fix nfs4_client_to_reclaim() to look the name up with
nfsd4_find_reclaim_client() first and, on a hit, fold the new
princhash into the existing record (if it lacks one) and return that
record without allocating or touching reclaim_str_hashtbl_size. On
kmemdup() failure during the fold-in, return NULL so
__cld_pipe_inprogress_downcall() surfaces -EFAULT to nfsdcld, matching
the miss-path contract.
Add an rw_semaphore (reclaim_str_hashtbl_lock) to struct nfsd_net that
serialises all access to reclaim_str_hashtbl[] and
reclaim_str_hashtbl_size. Writers (nfs4_client_to_reclaim,
nfs4_remove_reclaim_record callers) hold the write side; readers
(nfsd4_cld_check*, inc_reclaim_complete, clients_still_reclaiming,
nfs4_has_reclaimed_state, nfsd4_check_legacy_client) hold the read
side. All call sites are in sleepable context, and none is a hot
path, so the rwsem cost is negligible.
Reported-by: Chris Mason <clm@meta.com>
Fixes: 362063a595 ("nfsd: keep a tally of RECLAIM_COMPLETE operations when using nfsdcld")
Cc: stable@vger.kernel.org
Assisted-by: kres:claude-opus-4-7
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-4-f27e8eb4d974@kernel.org
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
parent
11a5fe42e1
commit
9b57793863
|
|
@ -93,6 +93,7 @@ struct nfsd_net {
|
|||
*/
|
||||
struct list_head *reclaim_str_hashtbl;
|
||||
int reclaim_str_hashtbl_size;
|
||||
struct rw_semaphore reclaim_str_hashtbl_lock;
|
||||
struct list_head *conf_id_hashtbl;
|
||||
struct rb_root conf_name_tree;
|
||||
struct list_head *unconf_id_hashtbl;
|
||||
|
|
@ -105,7 +106,10 @@ struct nfsd_net {
|
|||
* close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time
|
||||
* for last close replay.
|
||||
*
|
||||
* All of the above fields are protected by the client_mutex.
|
||||
* reclaim_str_hashtbl[], reclaim_str_hashtbl_size are protected by
|
||||
* reclaim_str_hashtbl_lock.
|
||||
*
|
||||
* All of the remaining fields are protected by the client_lock.
|
||||
*/
|
||||
struct list_head client_lru;
|
||||
struct list_head close_lru;
|
||||
|
|
|
|||
|
|
@ -285,10 +285,12 @@ __nfsd4_remove_reclaim_record_grace(const char *dname, int len,
|
|||
return;
|
||||
}
|
||||
name.len = len;
|
||||
down_write(&nn->reclaim_str_hashtbl_lock);
|
||||
crp = nfsd4_find_reclaim_client(name, nn);
|
||||
kfree(name.data);
|
||||
if (crp)
|
||||
nfs4_remove_reclaim_record(crp, nn);
|
||||
up_write(&nn->reclaim_str_hashtbl_lock);
|
||||
kfree(name.data);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -484,6 +486,7 @@ nfs4_legacy_state_init(struct net *net)
|
|||
for (i = 0; i < CLIENT_HASH_SIZE; i++)
|
||||
INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]);
|
||||
nn->reclaim_str_hashtbl_size = 0;
|
||||
init_rwsem(&nn->reclaim_str_hashtbl_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -598,13 +601,16 @@ nfsd4_check_legacy_client(struct nfs4_client *clp)
|
|||
goto out_enoent;
|
||||
}
|
||||
name.len = HEXDIR_LEN;
|
||||
down_read(&nn->reclaim_str_hashtbl_lock);
|
||||
crp = nfsd4_find_reclaim_client(name, nn);
|
||||
kfree(name.data);
|
||||
if (crp) {
|
||||
set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
|
||||
crp->cr_clp = clp;
|
||||
return 0;
|
||||
}
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
kfree(name.data);
|
||||
if (crp)
|
||||
return 0;
|
||||
|
||||
out_enoent:
|
||||
return -ENOENT;
|
||||
|
|
@ -1176,6 +1182,7 @@ nfsd4_cld_check(struct nfs4_client *clp)
|
|||
return 0;
|
||||
|
||||
/* look for it in the reclaim hashtable otherwise */
|
||||
down_read(&nn->reclaim_str_hashtbl_lock);
|
||||
crp = nfsd4_find_reclaim_client(clp->cl_name, nn);
|
||||
if (crp)
|
||||
goto found;
|
||||
|
|
@ -1191,6 +1198,7 @@ nfsd4_cld_check(struct nfs4_client *clp)
|
|||
if (!name.data) {
|
||||
dprintk("%s: failed to allocate memory for name.data!\n",
|
||||
__func__);
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
name.len = HEXDIR_LEN;
|
||||
|
|
@ -1201,9 +1209,11 @@ nfsd4_cld_check(struct nfs4_client *clp)
|
|||
|
||||
}
|
||||
#endif
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return -ENOENT;
|
||||
found:
|
||||
crp->cr_clp = clp;
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1215,6 +1225,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
|
|||
struct cld_net *cn = nn->cld_net;
|
||||
#endif
|
||||
struct nfs4_client_reclaim *crp;
|
||||
unsigned int princhashlen;
|
||||
char *principal = NULL;
|
||||
|
||||
/* did we already find that this client is stable? */
|
||||
|
|
@ -1222,6 +1233,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
|
|||
return 0;
|
||||
|
||||
/* look for it in the reclaim hashtable otherwise */
|
||||
down_read(&nn->reclaim_str_hashtbl_lock);
|
||||
crp = nfsd4_find_reclaim_client(clp->cl_name, nn);
|
||||
if (crp)
|
||||
goto found;
|
||||
|
|
@ -1237,6 +1249,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
|
|||
if (!name.data) {
|
||||
dprintk("%s: failed to allocate memory for name.data\n",
|
||||
__func__);
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
name.len = HEXDIR_LEN;
|
||||
|
|
@ -1247,23 +1260,31 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
|
|||
|
||||
}
|
||||
#endif
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return -ENOENT;
|
||||
found:
|
||||
if (crp->cr_princhash.len) {
|
||||
princhashlen = crp->cr_princhash.len;
|
||||
if (princhashlen) {
|
||||
u8 digest[SHA256_DIGEST_SIZE];
|
||||
u8 *pdata;
|
||||
|
||||
if (clp->cl_cred.cr_raw_principal)
|
||||
principal = clp->cl_cred.cr_raw_principal;
|
||||
else if (clp->cl_cred.cr_principal)
|
||||
principal = clp->cl_cred.cr_principal;
|
||||
if (principal == NULL)
|
||||
if (principal == NULL) {
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
sha256(principal, strlen(principal), digest);
|
||||
if (memcmp(crp->cr_princhash.data, digest,
|
||||
crp->cr_princhash.len))
|
||||
pdata = crp->cr_princhash.data;
|
||||
if (memcmp(pdata, digest, princhashlen)) {
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
crp->cr_clp = clp;
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1362,6 +1383,7 @@ nfs4_cld_state_init(struct net *net)
|
|||
for (i = 0; i < CLIENT_HASH_SIZE; i++)
|
||||
INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]);
|
||||
nn->reclaim_str_hashtbl_size = 0;
|
||||
init_rwsem(&nn->reclaim_str_hashtbl_lock);
|
||||
set_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags);
|
||||
atomic_set(&nn->nr_reclaim_complete, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -2677,14 +2677,21 @@ static void inc_reclaim_complete(struct nfs4_client *clp)
|
|||
|
||||
if (!test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags))
|
||||
return;
|
||||
if (!nfsd4_find_reclaim_client(clp->cl_name, nn))
|
||||
|
||||
down_read(&nn->reclaim_str_hashtbl_lock);
|
||||
if (!nfsd4_find_reclaim_client(clp->cl_name, nn)) {
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return;
|
||||
}
|
||||
if (atomic_inc_return(&nn->nr_reclaim_complete) ==
|
||||
nn->reclaim_str_hashtbl_size) {
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
printk(KERN_INFO "NFSD: all clients done reclaiming, ending NFSv4 grace period (net %x)\n",
|
||||
clp->net->ns.inum);
|
||||
nfsd4_end_grace(nn);
|
||||
return;
|
||||
}
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
}
|
||||
|
||||
static void expire_client(struct nfs4_client *clp)
|
||||
|
|
@ -6821,10 +6828,15 @@ static bool clients_still_reclaiming(struct nfsd_net *nn)
|
|||
|
||||
if (test_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags))
|
||||
return false;
|
||||
if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags) &&
|
||||
atomic_read(&nn->nr_reclaim_complete) ==
|
||||
nn->reclaim_str_hashtbl_size)
|
||||
return false;
|
||||
if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags)) {
|
||||
int size;
|
||||
|
||||
down_read(&nn->reclaim_str_hashtbl_lock);
|
||||
size = nn->reclaim_str_hashtbl_size;
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
if (atomic_read(&nn->nr_reclaim_complete) == size)
|
||||
return false;
|
||||
}
|
||||
if (!test_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags))
|
||||
return false;
|
||||
clear_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags);
|
||||
|
|
@ -8994,9 +9006,13 @@ bool
|
|||
nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
|
||||
{
|
||||
struct nfs4_client_reclaim *crp;
|
||||
bool found;
|
||||
|
||||
down_read(&nn->reclaim_str_hashtbl_lock);
|
||||
crp = nfsd4_find_reclaim_client(name, nn);
|
||||
return (crp && crp->cr_clp);
|
||||
found = (crp && crp->cr_clp);
|
||||
up_read(&nn->reclaim_str_hashtbl_lock);
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -9009,10 +9025,39 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
|
|||
unsigned int strhashval;
|
||||
struct nfs4_client_reclaim *crp;
|
||||
|
||||
down_write(&nn->reclaim_str_hashtbl_lock);
|
||||
|
||||
/*
|
||||
* A reclaim record for this client name may already exist (for
|
||||
* example, populated at boot from the recovery directory before
|
||||
* an in-grace RECLAIM_COMPLETE or an nfsdcld downcall delivers
|
||||
* the same name). Dedup here so reclaim_str_hashtbl_size stays
|
||||
* equal to the number of distinct client names; inc_reclaim_complete
|
||||
* relies on that equality to end the grace period via the fast path.
|
||||
*/
|
||||
crp = nfsd4_find_reclaim_client(name, nn);
|
||||
if (crp) {
|
||||
if (princhash.len && crp->cr_princhash.len == 0) {
|
||||
void *pdata = kmemdup(princhash.data, princhash.len,
|
||||
GFP_KERNEL);
|
||||
if (pdata) {
|
||||
crp->cr_princhash.data = pdata;
|
||||
crp->cr_princhash.len = princhash.len;
|
||||
} else {
|
||||
dprintk("%s: failed to allocate memory for princhash.data!\n",
|
||||
__func__);
|
||||
crp = NULL;
|
||||
}
|
||||
}
|
||||
up_write(&nn->reclaim_str_hashtbl_lock);
|
||||
return crp;
|
||||
}
|
||||
|
||||
name.data = kmemdup(name.data, name.len, GFP_KERNEL);
|
||||
if (!name.data) {
|
||||
dprintk("%s: failed to allocate memory for name.data!\n",
|
||||
__func__);
|
||||
up_write(&nn->reclaim_str_hashtbl_lock);
|
||||
return NULL;
|
||||
}
|
||||
if (princhash.len) {
|
||||
|
|
@ -9021,6 +9066,7 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
|
|||
dprintk("%s: failed to allocate memory for princhash.data!\n",
|
||||
__func__);
|
||||
kfree(name.data);
|
||||
up_write(&nn->reclaim_str_hashtbl_lock);
|
||||
return NULL;
|
||||
}
|
||||
} else
|
||||
|
|
@ -9040,6 +9086,7 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
|
|||
kfree(name.data);
|
||||
kfree(princhash.data);
|
||||
}
|
||||
up_write(&nn->reclaim_str_hashtbl_lock);
|
||||
return crp;
|
||||
}
|
||||
|
||||
|
|
@ -9059,6 +9106,7 @@ nfs4_release_reclaim(struct nfsd_net *nn)
|
|||
struct nfs4_client_reclaim *crp = NULL;
|
||||
int i;
|
||||
|
||||
down_write(&nn->reclaim_str_hashtbl_lock);
|
||||
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
|
||||
while (!list_empty(&nn->reclaim_str_hashtbl[i])) {
|
||||
crp = list_entry(nn->reclaim_str_hashtbl[i].next,
|
||||
|
|
@ -9067,6 +9115,7 @@ nfs4_release_reclaim(struct nfsd_net *nn)
|
|||
}
|
||||
}
|
||||
WARN_ON_ONCE(nn->reclaim_str_hashtbl_size);
|
||||
up_write(&nn->reclaim_str_hashtbl_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user