nfsd_mutex serializes the entire NFSD control plane across every network
namespace. The nfsd genl family sets .parallel_ops, so it is the only
serialization there: one container starting nfsd, or one long
RPC_STATUS_GET dump, stalls every other namespace's admin operations. The
nfsd threads suffer too -- the dynamic-thread autoscaler trylocks the same
mutex on every -ETIMEDOUT and -EBUSY, and a failed trylock skips the
spawn/reap entirely.

Almost none of what the mutex covers is actually shared. Add
nn->nfsd_mutex for the per-namespace control plane:

  - nn->nfsd_serv and the svc_serv members hanging off it (->sv_permsocks,
    ->sv_temp_socks, per-pool thread counts)
  - NFSD_NET_UP / NFSD_NET_LOCKD_UP
  - the settables that may only change while the server is down
    (->nfsd_versions, ->nfsd4_lease, ->nfsd4_grace, ->max_blksize, ...)
  - nn->svc_export_cache / nn->svc_expkey_cache liveness
  - nn->conf_id_hashtbl liveness, for the state-revoke walks

The global nfsd_mutex keeps only what is genuinely host-wide: the
nfsd_users refcount and the resources it brings up (open file cache, NFSv4
global tables), and the address-notifier registration.
nfsd_startup_generic()/nfsd_shutdown_generic() and the notifier
register/unregister now take it internally, so per-net callers never see
it. nfsd_file_cache_purge() likewise takes it itself, which lets
expkey_flush() drop its hand-rolled lock.

write_recoverydir() still takes the global mutex, but that now serializes
writers only. The startup readers of user_recovery_dirname --
nfsd4_init_recdir() and check_for_legacy_methods() -- run under
nn->nfsd_mutex alone, so a write from one namespace can tear the string
under another namespace's startup. Worst case is a bogus path and a
spurious startup error; the buffer is always NUL-terminated in bounds, so
there is nothing to overrun. Left alone deliberately: that global already
had lock-free readers in nfsd4_cltrack_legacy_{topdir,recdir}(), and legacy
client tracking is deprecated and effectively init-netns-only -- its
usermodehelper upcall always runs in the init mount namespace, so the
stored path can only ever name an init-ns path.

Lock ordering is nn->nfsd_mutex outside the global nfsd_mutex; nothing
takes two namespaces' nfsd_mutexes. struct svc_info already indirects
through a mutex pointer, so pool_stats needs only to be pointed at the new
lock.

The notifier refcount becomes a plain int now that it is genuinely
mutex-guarded -- an atomic was never enough to serialize the
register/unregister against the count. nfsd_create_serv() now takes that
reference before publishing nn->nfsd_serv: namespaces are no longer
serialized against each other here, so ordering it the other way would let
the count dip to zero while another namespace's serv is already visible.

Note: the RPC thread pool needs no global lock here. svc_pool_map has its
own svc_pool_map_mutex and per-pool thread counts live in the per-net
svc_serv.

Assisted-by: LLM
Signed-off-by: Jeff Layton <[email protected]>
---
 fs/nfsd/export.c                                   |  22 ++--
 fs/nfsd/filecache.c                                |   5 +-
 fs/nfsd/netns.h                                    |  10 ++
 fs/nfsd/nfs4proc.c                                 |   4 +-
 fs/nfsd/nfs4state.c                                |   8 +-
 fs/nfsd/nfsctl.c                                   | 117 ++++++++++---------
 fs/nfsd/nfssvc.c                                   | 129 ++++++++++++++-------
 .../testing/selftests/nfsd/nfsd_netlink_listener.c |   6 +-
 8 files changed, 180 insertions(+), 121 deletions(-)

diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index e5a0f1ababe6..265ea8fd31c6 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -248,13 +248,7 @@ static struct cache_head *expkey_alloc(void)
 
 static void expkey_flush(void)
 {
-       /*
-        * Take the nfsd_mutex here to ensure that the file cache is not
-        * destroyed while we're in the middle of flushing.
-        */
-       mutex_lock(&nfsd_mutex);
        nfsd_file_cache_purge(current->nsproxy->net_ns);
-       mutex_unlock(&nfsd_mutex);
 }
 
 static int expkey_notify(struct cache_detail *cd, struct cache_head *h)
@@ -346,7 +340,7 @@ int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb,
 
        nn = net_generic(sock_net(skb->sk), nfsd_net_id);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        cd = nn->svc_expkey_cache;
        if (!cd) {
@@ -425,7 +419,7 @@ int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb,
        kfree(seqnos);
        kfree(items);
 out_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return ret;
 }
 
@@ -560,7 +554,7 @@ int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb,
 
        nn = net_generic(genl_info_net(info), nfsd_net_id);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        cd = nn->svc_expkey_cache;
        if (!cd) {
@@ -576,7 +570,7 @@ int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb,
        }
 
 out_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return ret;
 }
 
@@ -673,7 +667,7 @@ int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb,
 
        nn = net_generic(sock_net(skb->sk), nfsd_net_id);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        cd = nn->svc_export_cache;
        if (!cd) {
@@ -757,7 +751,7 @@ int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb,
        kfree(seqnos);
        kfree(items);
 out_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return ret;
 }
 
@@ -1056,7 +1050,7 @@ int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
 
        nn = net_generic(genl_info_net(info), nfsd_net_id);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        cd = nn->svc_export_cache;
        if (!cd) {
@@ -1072,7 +1066,7 @@ int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
        }
 
 out_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return ret;
 }
 
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 17a94e6fcb15..79b9e8c92e70 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -1011,13 +1011,16 @@ nfsd_file_cache_start_net(struct net *net)
  * nfsd_file_cache_purge - Remove all cache items associated with @net
  * @net: target net namespace
  *
+ * Takes nfsd_mutex so the cache cannot be torn down underneath the
+ * walk.  Callers must not already hold it.
  */
 void
 nfsd_file_cache_purge(struct net *net)
 {
-       lockdep_assert_held(&nfsd_mutex);
+       mutex_lock(&nfsd_mutex);
        if (test_bit(NFSD_FILE_CACHE_UP, &nfsd_file_flags) == 1)
                __nfsd_file_cache_purge(net);
+       mutex_unlock(&nfsd_mutex);
 }
 
 void
diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h
index 374ce83e2ba0..35199c17f8d1 100644
--- a/fs/nfsd/netns.h
+++ b/fs/nfsd/netns.h
@@ -164,6 +164,16 @@ struct nfsd_net {
        u32 clientid_counter;
        u32 clverifier_counter;
 
+       /*
+        * Serializes this namespace's control plane: ->nfsd_serv and the
+        * svc_serv members that hang off it (->sv_permsocks,
+        * ->sv_temp_socks, thread counts), the NFSD_NET_* flags, and the
+        * settables above that may only change while the server is down.
+        *
+        * Nests outside the global nfsd_mutex.
+        */
+       struct mutex nfsd_mutex;
+
        struct svc_info nfsd_info;
 #define nfsd_serv nfsd_info.serv
 
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 3a82af381a8d..7df60abfbff1 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1703,7 +1703,7 @@ static bool nfsd4_copy_on_sb(const struct nfsd4_copy 
*copy,
  * @net: net namespace containing the copy operations
  * @sb: targeted superblock
  *
- * Context: Caller must hold nfsd_mutex with NFSD_NET_UP set.  Outside
+ * Context: Caller must hold nn->nfsd_mutex with NFSD_NET_UP set.  Outside
  *          that window nn->conf_id_hashtbl is unallocated or freed,
  *          so the walk would dereference a NULL or dangling pointer.
  */
@@ -1715,7 +1715,7 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct 
super_block *sb)
        unsigned int idhashval;
        LIST_HEAD(to_cancel);
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
        spin_lock(&nn->client_lock);
        for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) {
                struct list_head *head = &nn->conf_id_hashtbl[idhashval];
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 1de6c6d757c3..0f9340eb281e 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -2102,7 +2102,7 @@ static void revoke_one_stid(struct nfsd_net *nn, struct 
nfs4_client *clp,
  * The clients which own the states will subsequently be notified that the
  * states have been "admin-revoked".
  *
- * Context: Caller must hold nfsd_mutex with NFSD_NET_UP set.  Outside
+ * Context: Caller must hold nn->nfsd_mutex with NFSD_NET_UP set.  Outside
  *          that window nn->conf_id_hashtbl is unallocated or freed,
  *          so the walk would dereference a NULL or dangling pointer.
  */
@@ -2111,7 +2111,7 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct 
super_block *sb)
        unsigned int idhashval;
        unsigned int sc_types;
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
        sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT;
 
@@ -2190,7 +2190,7 @@ static struct nfs4_stid *find_one_export_stid(struct 
nfs4_client *clp,
  * Userspace (exportfs -u) sends this after removing the last client
  * for a path, enabling the underlying filesystem to be unmounted.
  *
- * Context: Caller must hold nfsd_mutex with NFSD_NET_UP set.  Outside
+ * Context: Caller must hold nn->nfsd_mutex with NFSD_NET_UP set.  Outside
  *          that window nn->conf_id_hashtbl is unallocated or freed,
  *          so the walk would dereference a NULL or dangling pointer.
  */
@@ -2199,7 +2199,7 @@ void nfsd4_revoke_export_states(struct nfsd_net *nn, 
const struct path *path)
        unsigned int idhashval;
        unsigned int sc_types;
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
        sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT;
 
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 330d0f12e199..5ae33c21cf71 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -302,15 +302,15 @@ static ssize_t write_unlock_fs(struct file *file, char 
*buf, size_t size)
         * 3.  Is that directory the root of an exported file system?
         */
        error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb);
-       mutex_lock(&nfsd_mutex);
        nn = net_generic(netns(file), nfsd_net_id);
+       mutex_lock(&nn->nfsd_mutex);
        if (test_bit(NFSD_NET_UP, &nn->flags)) {
                nfsd4_cancel_copy_by_sb(netns(file), path.dentry->d_sb);
                nfsd4_revoke_states(nn, path.dentry->d_sb);
        } else {
                error = -EINVAL;
        }
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        path_put(&path);
        return error;
@@ -436,12 +436,12 @@ static ssize_t write_threads(struct file *file, char 
*buf, size_t size)
                if (newthreads < 0)
                        return -EINVAL;
                trace_nfsd_ctl_threads(net, newthreads);
-               mutex_lock(&nfsd_mutex);
+               mutex_lock(&nn->nfsd_mutex);
                if (newthreads > 0 || nn->nfsd_serv != NULL)
                        rv = nfsd_svc(1, &newthreads, net, file->f_cred, NULL);
                else
                        rv = 0;
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
                if (rv < 0)
                        return rv;
        } else
@@ -484,8 +484,9 @@ static ssize_t write_pool_threads(struct file *file, char 
*buf, size_t size)
        int npools;
        int *nthreads;
        struct net *net = netns(file);
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        npools = nfsd_nrpools(net);
        if (npools == 0) {
                /*
@@ -493,7 +494,7 @@ static ssize_t write_pool_threads(struct file *file, char 
*buf, size_t size)
                 * writing to the threads file but NOT the pool_threads
                 * file, sorry.  Report zero threads.
                 */
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
                strcpy(buf, "0\n");
                return strlen(buf);
        }
@@ -547,7 +548,7 @@ static ssize_t write_pool_threads(struct file *file, char 
*buf, size_t size)
        rv = mesg - buf;
 out_free:
        kfree(nthreads);
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return rv;
 }
 
@@ -710,11 +711,12 @@ static ssize_t __write_versions(struct file *file, char 
*buf, size_t size)
  */
 static ssize_t write_versions(struct file *file, char *buf, size_t size)
 {
+       struct nfsd_net *nn = net_generic(netns(file), nfsd_net_id);
        ssize_t rv;
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        rv = __write_versions(file, buf, size);
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return rv;
 }
 
@@ -876,11 +878,12 @@ static ssize_t __write_ports(struct file *file, char 
*buf, size_t size,
  */
 static ssize_t write_ports(struct file *file, char *buf, size_t size)
 {
+       struct nfsd_net *nn = net_generic(netns(file), nfsd_net_id);
        ssize_t rv;
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        rv = __write_ports(file, buf, size, netns(file));
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return rv;
 }
 
@@ -924,13 +927,13 @@ static ssize_t write_maxblksize(struct file *file, char 
*buf, size_t size)
                bsize = max_t(int, bsize, 1024);
                bsize = min_t(int, bsize, NFSSVC_MAXBLKSIZE);
                bsize &= ~(1024-1);
-               mutex_lock(&nfsd_mutex);
+               mutex_lock(&nn->nfsd_mutex);
                if (nn->nfsd_serv) {
-                       mutex_unlock(&nfsd_mutex);
+                       mutex_unlock(&nn->nfsd_mutex);
                        return -EBUSY;
                }
                nn->max_blksize = bsize;
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
        }
 
        return scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%u\n",
@@ -979,9 +982,9 @@ static ssize_t nfsd4_write_time(struct file *file, char 
*buf, size_t size,
 {
        ssize_t rv;
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        rv = __nfsd4_write_time(file, buf, size, time, nn);
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return rv;
 }
 
@@ -1084,9 +1087,15 @@ static ssize_t write_recoverydir(struct file *file, char 
*buf, size_t size)
        ssize_t rv;
        struct nfsd_net *nn = net_generic(netns(file), nfsd_net_id);
 
+       /*
+        * nn->nfsd_mutex guards the nn->nfsd_serv check; the recovery
+        * dirname itself is still shared between namespaces.
+        */
+       mutex_lock(&nn->nfsd_mutex);
        mutex_lock(&nfsd_mutex);
        rv = __write_recoverydir(file, buf, size, nn);
        mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return rv;
 }
 #endif
@@ -1530,9 +1539,9 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
        int i, ret, rqstp_index = 0;
        struct nfsd_net *nn;
 
-       mutex_lock(&nfsd_mutex);
-
        nn = net_generic(sock_net(skb->sk), nfsd_net_id);
+
+       mutex_lock(&nn->nfsd_mutex);
        if (!nn->nfsd_serv) {
                ret = -ENODEV;
                goto out_unlock;
@@ -1649,7 +1658,7 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
 out:
        rcu_read_unlock();
 out_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        return ret;
 }
@@ -1659,7 +1668,7 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
  * @attr: nlattr NFSD_A_SERVER_FH_KEY
  * @nn: nfsd_net
  *
- * Callers should hold nfsd_mutex, returns 0 on success or negative errno.
+ * Callers should hold nn->nfsd_mutex, returns 0 on success or negative errno.
  * Callers must ensure the server is shut down (sv_nrthreads == 0),
  * userspace documentation asserts the key may only be set when the server
  * is not running.
@@ -1714,7 +1723,7 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct 
genl_info *info)
                                 GENL_HDRLEN, rem)
                nrpools++;
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        nthreads = kzalloc_objs(int, nrpools);
        if (!nthreads) {
@@ -1779,7 +1788,7 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct 
genl_info *info)
        if (ret > 0)
                ret = 0;
 out_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        kfree(nthreads);
        return ret;
 }
@@ -1808,7 +1817,7 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct 
genl_info *info)
                goto err_free_msg;
        }
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        err = nla_put_u32(skb, NFSD_A_SERVER_GRACETIME,
                          nn->nfsd4_grace) ||
@@ -1838,14 +1847,14 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, 
struct genl_info *info)
                        goto err_unlock;
        }
 
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        genlmsg_end(skb, hdr);
 
        return genlmsg_reply(skb, info);
 
 err_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 err_free_msg:
        nlmsg_free(skb);
 
@@ -1868,11 +1877,11 @@ int nfsd_nl_version_set_doit(struct sk_buff *skb, 
struct genl_info *info)
        if (GENL_REQ_ATTR_CHECK(info, NFSD_A_SERVER_PROTO_VERSION))
                return -EINVAL;
 
-       mutex_lock(&nfsd_mutex);
-
        nn = net_generic(genl_info_net(info), nfsd_net_id);
+
+       mutex_lock(&nn->nfsd_mutex);
        if (nn->nfsd_serv) {
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
                return -EBUSY;
        }
 
@@ -1915,7 +1924,7 @@ int nfsd_nl_version_set_doit(struct sk_buff *skb, struct 
genl_info *info)
                }
        }
 
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        return 0;
 }
@@ -1943,9 +1952,9 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, struct 
genl_info *info)
                goto err_free_msg;
        }
 
-       mutex_lock(&nfsd_mutex);
        nn = net_generic(genl_info_net(info), nfsd_net_id);
 
+       mutex_lock(&nn->nfsd_mutex);
        for (i = 2; i <= 4; i++) {
                int j;
 
@@ -1987,13 +1996,13 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, 
struct genl_info *info)
                }
        }
 
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        genlmsg_end(skb, hdr);
 
        return genlmsg_reply(skb, info);
 
 err_nfsd_unlock:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 err_free_msg:
        nlmsg_free(skb);
 
@@ -2004,7 +2013,7 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, struct 
genl_info *info)
  * Transport classes NFSD knows how to instantiate. Vetting the name here
  * keeps a bogus string from reaching svc_xprt_create_from_sa(), where an
  * unknown name triggers a request_module("svc%s", name) upcall under
- * nfsd_mutex.
+ * nn->nfsd_mutex.
  */
 static bool nfsd_nl_transport_supported(const char *name)
 {
@@ -2085,14 +2094,15 @@ static int nfsd_nl_validate_listeners(struct genl_info 
*info)
        return count;
 }
 
-static size_t nfsd_nl_listener_set_msgsize(struct svc_serv *serv)
+static size_t nfsd_nl_listener_set_msgsize(struct nfsd_net *nn,
+                                          struct svc_serv *serv)
 {
        size_t size = GENL_HDRLEN +                 /* genlmsg_iput() */
                      nla_total_size(0);            /* userspace-rpcbind */
        struct svc_xprt *xprt;
        unsigned int p;
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
        for (p = 0; p < serv->sv_nprogs; p++)
                size += serv->sv_programs[p].pg_nvers *
@@ -2118,15 +2128,16 @@ static struct sk_buff *
 nfsd_nl_listener_set_msg(struct genl_info *info, struct net *net,
                         struct svc_serv *serv)
 {
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
        struct svc_xprt *xprt;
        struct sk_buff *skb;
        unsigned int p, i;
        void *hdr;
        int err;
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
-       skb = genlmsg_new(nfsd_nl_listener_set_msgsize(serv), GFP_KERNEL);
+       skb = genlmsg_new(nfsd_nl_listener_set_msgsize(nn, serv), GFP_KERNEL);
        if (!skb)
                return ERR_PTR(-ENOMEM);
 
@@ -2244,10 +2255,10 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, 
struct genl_info *info)
 
        userspace_rpcbind = 
nla_get_flag(info->attrs[NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND]);
 
-       mutex_lock(&nfsd_mutex);
-
        nn = net_generic(net, nfsd_net_id);
 
+       mutex_lock(&nn->nfsd_mutex);
+
        /*
         * An empty list destroys the serv, and nfsd_destroy_serv() drops
         * whatever svc_bind() took either way, so teardown is not an
@@ -2258,13 +2269,13 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, 
struct genl_info *info)
            nn->nfsd_serv->sv_no_rpcbind != userspace_rpcbind) {
                NL_SET_ERR_MSG(info->extack,
                               "cannot change rpcbind ownership while a server 
exists");
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
                return -EBUSY;
        }
 
        err = nfsd_create_serv(net, userspace_rpcbind);
        if (err) {
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
                return err;
        }
 
@@ -2433,7 +2444,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct 
genl_info *info)
                nfsd_destroy_serv(net);
 
 out_unlock_mtx:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        /* rskb is only built once err is known to be zero. */
        if (rskb)
@@ -2467,9 +2478,9 @@ int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct 
genl_info *info)
                goto err_free_msg;
        }
 
-       mutex_lock(&nfsd_mutex);
        nn = net_generic(genl_info_net(info), nfsd_net_id);
 
+       mutex_lock(&nn->nfsd_mutex);
        /* no nfs server? Just send empty socket list */
        if (!nn->nfsd_serv)
                goto out_unlock_mtx;
@@ -2498,14 +2509,14 @@ int nfsd_nl_listener_get_doit(struct sk_buff *skb, 
struct genl_info *info)
        }
        spin_unlock_bh(&serv->sv_lock);
 out_unlock_mtx:
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        genlmsg_end(skb, hdr);
 
        return genlmsg_reply(skb, info);
 
 err_serv_unlock:
        spin_unlock_bh(&serv->sv_lock);
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 err_free_msg:
        nlmsg_free(skb);
 
@@ -2589,7 +2600,7 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct 
genl_info *info)
        if (info->attrs[NFSD_A_CACHE_FLUSH_MASK])
                mask = nla_get_u32(info->attrs[NFSD_A_CACHE_FLUSH_MASK]);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
 
        if ((mask & NFSD_CACHE_TYPE_SVC_EXPORT) &&
            nn->svc_export_cache)
@@ -2599,7 +2610,7 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct 
genl_info *info)
            nn->svc_expkey_cache)
                cache_purge(nn->svc_expkey_cache);
 
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        return 0;
 }
@@ -2947,14 +2958,14 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb,
 
        error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb);
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        if (test_bit(NFSD_NET_UP, &nn->flags)) {
                nfsd4_cancel_copy_by_sb(net, path.dentry->d_sb);
                nfsd4_revoke_states(nn, path.dentry->d_sb);
        } else {
                error = -EINVAL;
        }
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        path_put(&path);
        return error;
@@ -2994,13 +3005,13 @@ int nfsd_nl_unlock_export_doit(struct sk_buff *skb, 
struct genl_info *info)
        if (error)
                return error;
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        if (test_bit(NFSD_NET_UP, &nn->flags)) {
                nfsd_file_close_export(net, &path);
                nfsd4_revoke_export_states(nn, &path);
        } else
                error = -EINVAL;
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 
        path_put(&path);
        return error;
@@ -3057,7 +3068,8 @@ static __net_init int nfsd_net_init(struct net *net)
                nn->nfsd_versions[i] = nfsd_support_version(i);
        for (i = 0; i < sizeof(nn->nfsd4_minorversions); i++)
                nn->nfsd4_minorversions[i] = nfsd_support_version(4);
-       nn->nfsd_info.mutex = &nfsd_mutex;
+       mutex_init(&nn->nfsd_mutex);
+       nn->nfsd_info.mutex = &nn->nfsd_mutex;
        nn->nfsd_serv = NULL;
        nfsd4_init_leases_net(nn);
        get_random_bytes(&nn->siphash_key, sizeof(nn->siphash_key));
@@ -3121,6 +3133,7 @@ static __net_exit void nfsd_net_exit(struct net *net)
        percpu_counter_destroy_many(nn->counter, NFSD_STATS_COUNTERS_NUM);
        nfsd_idmap_shutdown(net);
        nfsd_export_shutdown(net);
+       mutex_destroy(&nn->nfsd_mutex);
 }
 
 static struct pernet_operations nfsd_net_ops = {
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index 77e1e6ba686d..3d47e5c86bd5 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -55,16 +55,22 @@ static __be32                       
nfsd_init_request(struct svc_rqst *,
                                                struct svc_process_info *);
 
 /*
- * nfsd_mutex protects nn->nfsd_serv -- both the pointer itself and some 
members
- * of the svc_serv struct such as ->sv_temp_socks and ->sv_permsocks.
+ * NFSD's control plane is serialized by two mutexes.
  *
- * Finally, the nfsd_mutex also protects some of the global variables that are
- * accessed when nfsd starts and that are settable via the write_* routines in
- * nfsctl.c. In particular:
+ * Nearly everything is per-namespace and belongs to nn->nfsd_mutex: the
+ * nn->nfsd_serv pointer and the svc_serv members that hang off it
+ * (->sv_permsocks, ->sv_temp_socks, per-pool thread counts), the
+ * NFSD_NET_* flags, and the nfsd_net settables that may only change while
+ * that namespace's server is down (->nfsd_versions, ->nfsd4_lease,
+ * ->nfsd4_grace, ->max_blksize, ...).
  *
- *     user_recovery_dirname
- *     user_lease_time
- *     nfsd_versions
+ * The global nfsd_mutex covers only what is genuinely shared between
+ * namespaces: the nfsd_users refcount and the host-wide resources it
+ * brings up and tears down (the open file cache and the NFSv4 global
+ * tables), the address-notifier registration, and user_recovery_dirname.
+ *
+ * Lock ordering is nn->nfsd_mutex outside the global nfsd_mutex.  Nothing
+ * takes two namespaces' nfsd_mutexes.
  */
 DEFINE_MUTEX(nfsd_mutex);
 
@@ -251,20 +257,23 @@ int nfsd_nrthreads(struct net *net)
        int rv = 0;
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
-       /* nfsd_mutex keeps nn->nfsd_serv valid across the read. */
-       mutex_lock(&nfsd_mutex);
+       /* nn->nfsd_mutex keeps nn->nfsd_serv valid across the read. */
+       mutex_lock(&nn->nfsd_mutex);
        if (nn->nfsd_serv)
                rv = svc_serv_maxthreads(nn->nfsd_serv);
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
        return rv;
 }
 
+/* Number of namespaces holding the host-wide resources up */
 static int nfsd_users = 0;
 
-static int nfsd_startup_generic(void)
+static int __nfsd_startup_generic(void)
 {
        int ret;
 
+       lockdep_assert_held(&nfsd_mutex);
+
        if (nfsd_users++)
                return 0;
 
@@ -284,13 +293,24 @@ static int nfsd_startup_generic(void)
        return ret;
 }
 
-static void nfsd_shutdown_generic(void)
+static int nfsd_startup_generic(void)
 {
-       if (--nfsd_users)
-               return;
+       int ret;
 
-       nfs4_state_shutdown();
-       nfsd_file_cache_shutdown();
+       mutex_lock(&nfsd_mutex);
+       ret = __nfsd_startup_generic();
+       mutex_unlock(&nfsd_mutex);
+       return ret;
+}
+
+static void nfsd_shutdown_generic(void)
+{
+       mutex_lock(&nfsd_mutex);
+       if (!--nfsd_users) {
+               nfs4_state_shutdown();
+               nfsd_file_cache_shutdown();
+       }
+       mutex_unlock(&nfsd_mutex);
 }
 
 static bool nfsd_needs_lockd(struct nfsd_net *nn)
@@ -505,8 +525,32 @@ static struct notifier_block nfsd_inet6addr_notifier = {
 };
 #endif
 
-/* Only used under nfsd_mutex, so this atomic may be overkill: */
-static atomic_t nfsd_notifier_refcount = ATOMIC_INIT(0);
+/* Number of namespaces with a serv, guarded by nfsd_mutex */
+static int nfsd_notifier_users;
+
+static void nfsd_register_notifiers(void)
+{
+       mutex_lock(&nfsd_mutex);
+       if (!nfsd_notifier_users++) {
+               register_inetaddr_notifier(&nfsd_inetaddr_notifier);
+#if IS_ENABLED(CONFIG_IPV6)
+               register_inet6addr_notifier(&nfsd_inet6addr_notifier);
+#endif
+       }
+       mutex_unlock(&nfsd_mutex);
+}
+
+static void nfsd_unregister_notifiers(void)
+{
+       mutex_lock(&nfsd_mutex);
+       if (!--nfsd_notifier_users) {
+               unregister_inetaddr_notifier(&nfsd_inetaddr_notifier);
+#if IS_ENABLED(CONFIG_IPV6)
+               unregister_inet6addr_notifier(&nfsd_inet6addr_notifier);
+#endif
+       }
+       mutex_unlock(&nfsd_mutex);
+}
 
 /**
  * nfsd_destroy_serv - tear down NFSD's svc_serv for a namespace
@@ -517,19 +561,13 @@ void nfsd_destroy_serv(struct net *net)
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
        struct svc_serv *serv = nn->nfsd_serv;
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
        spin_lock(&nfsd_notifier_lock);
        nn->nfsd_serv = NULL;
        spin_unlock(&nfsd_notifier_lock);
 
-       /* check if the notifier still has clients */
-       if (atomic_dec_return(&nfsd_notifier_refcount) == 0) {
-               unregister_inetaddr_notifier(&nfsd_inetaddr_notifier);
-#if IS_ENABLED(CONFIG_IPV6)
-               unregister_inet6addr_notifier(&nfsd_inet6addr_notifier);
-#endif
-       }
+       nfsd_unregister_notifiers();
 
        /*
         * write_ports can create the server without actually starting
@@ -586,17 +624,17 @@ void nfsd_shutdown_threads(struct net *net)
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
        struct svc_serv *serv;
 
-       mutex_lock(&nfsd_mutex);
+       mutex_lock(&nn->nfsd_mutex);
        serv = nn->nfsd_serv;
        if (serv == NULL) {
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
                return;
        }
 
        /* Kill outstanding nfsd threads */
        svc_set_num_threads(serv, 0, 0);
        nfsd_destroy_serv(net);
-       mutex_unlock(&nfsd_mutex);
+       mutex_unlock(&nn->nfsd_mutex);
 }
 
 struct svc_rqst *nfsd_current_rqst(void)
@@ -619,7 +657,7 @@ int nfsd_create_serv(struct net *net, bool no_rpcbind)
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
        struct svc_serv *serv;
 
-       WARN_ON(!mutex_is_locked(&nfsd_mutex));
+       WARN_ON(!mutex_is_locked(&nn->nfsd_mutex));
        if (nn->nfsd_serv)
                return 0;
 
@@ -650,17 +688,18 @@ int nfsd_create_serv(struct net *net, bool no_rpcbind)
                percpu_ref_exit(&nn->nfsd_net_ref);
                return error;
        }
+       /*
+        * Register before publishing nn->nfsd_serv.  Namespaces are only
+        * serialized against each other by nfsd_mutex here, so
+        * taking the reference first is what guarantees a visible
+        * nn->nfsd_serv never coincides with an unregistered notifier.
+        */
+       nfsd_register_notifiers();
+
        spin_lock(&nfsd_notifier_lock);
        nn->nfsd_serv = serv;
        spin_unlock(&nfsd_notifier_lock);
 
-       /* check if the notifier is already set */
-       if (atomic_inc_return(&nfsd_notifier_refcount) == 1) {
-               register_inetaddr_notifier(&nfsd_inetaddr_notifier);
-#if IS_ENABLED(CONFIG_IPV6)
-               register_inet6addr_notifier(&nfsd_inet6addr_notifier);
-#endif
-       }
        nfsd_reset_write_verifier(nn);
        return 0;
 }
@@ -707,7 +746,7 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net 
*net)
        int err = 0;
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
        if (nn->nfsd_serv == NULL || n <= 0)
                return 0;
@@ -777,7 +816,7 @@ nfsd_svc(int n, int *nthreads, struct net *net, const 
struct cred *cred, const c
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
        struct svc_serv *serv;
 
-       lockdep_assert_held(&nfsd_mutex);
+       lockdep_assert_held(&nn->nfsd_mutex);
 
        dprintk("nfsd: creating service\n");
 
@@ -955,13 +994,13 @@ nfsd(void *vrqstp)
                switch (svc_recv(rqstp, 5 * HZ)) {
                case -ETIMEDOUT:
                        /* No work arrived within the timeout window */
-                       if (mutex_trylock(&nfsd_mutex)) {
+                       if (mutex_trylock(&nn->nfsd_mutex)) {
                                if (pool->sp_nrthreads > pool->sp_nrthrmin) {
                                        trace_nfsd_dynthread_kill(net, pool);
                                        set_bit(RQ_VICTIM, &rqstp->rq_flags);
                                        have_mutex = true;
                                } else {
-                                       mutex_unlock(&nfsd_mutex);
+                                       mutex_unlock(&nn->nfsd_mutex);
                                }
                        } else {
                                trace_nfsd_dynthread_trylock_fail(net, pool);
@@ -970,7 +1009,7 @@ nfsd(void *vrqstp)
                case -EBUSY:
                        /* No idle threads; consider spawning another */
                        if (pool->sp_nrthreads < pool->sp_nrthrmax) {
-                               if (mutex_trylock(&nfsd_mutex)) {
+                               if (mutex_trylock(&nn->nfsd_mutex)) {
                                        if (pool->sp_nrthreads < 
pool->sp_nrthrmax) {
                                                int ret;
 
@@ -980,7 +1019,7 @@ nfsd(void *vrqstp)
                                                        
pr_notice_ratelimited("%s: unable to spawn new thread: %d\n",
                                                                              
__func__, ret);
                                        }
-                                       mutex_unlock(&nfsd_mutex);
+                                       mutex_unlock(&nn->nfsd_mutex);
                                } else {
                                        trace_nfsd_dynthread_trylock_fail(net, 
pool);
                                }
@@ -998,7 +1037,7 @@ nfsd(void *vrqstp)
        /* Release the thread */
        svc_exit_thread(rqstp);
        if (have_mutex)
-               mutex_unlock(&nfsd_mutex);
+               mutex_unlock(&nn->nfsd_mutex);
        return 0;
 }
 
diff --git a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c 
b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
index bef7e8b1ee71..1294057b6f62 100644
--- a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
+++ b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
@@ -5,7 +5,7 @@
  *
  * Three groups:
  *   validation  - malformed/abusive LISTENER_SET requests are rejected by
- *                 nfsd_nl_validate_listeners(), before nfsd_mutex is taken.
+ *                 nfsd_nl_validate_listeners(), before nn->nfsd_mutex is 
taken.
  *   functional  - create/add/remove listeners and verify LISTENER_GET
  *                 reflects the set (round-trip of transport + addr:port).
  *   semantics   - once threads are running (THREADS_SET) a listener change
@@ -949,8 +949,8 @@ TEST_F(nfsd_listener, val_missing_transport)
 }
 
 /*
- * A name matching no transport class must be refused before nfsd_mutex is
- * taken, so it never reaches svc_xprt_create_from_sa() and its
+ * A name matching no transport class must be refused before nn->nfsd_mutex
+ * is taken, so it never reaches svc_xprt_create_from_sa() and its
  * request_module("svc%s", name) upcall.
  *
  * The errno cannot show that -- svc_xprt_create_from_sa() returns

-- 
2.55.0


Reply via email to