]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - fs/nfsd/nfssvc.c
nfsd: Allow containers to set supported nfs versions
[mirror_ubuntu-jammy-kernel.git] / fs / nfsd / nfssvc.c
index 378edcfe9701a34f28d2185ef3663debdf939ee5..5207577746146c86bf7c2f88711620aa9d97d0ca 100644 (file)
@@ -38,12 +38,18 @@ static int                  nfsd_acl_rpcbind_set(struct net *,
                                                     u32, int,
                                                     unsigned short,
                                                     unsigned short);
+static __be32                  nfsd_acl_init_request(struct svc_rqst *,
+                                               const struct svc_program *,
+                                               struct svc_process_info *);
 #endif
 static int                     nfsd_rpcbind_set(struct net *,
                                                 const struct svc_program *,
                                                 u32, int,
                                                 unsigned short,
                                                 unsigned short);
+static __be32                  nfsd_init_request(struct svc_rqst *,
+                                               const struct svc_program *,
+                                               struct svc_process_info *);
 
 /*
  * nfsd_mutex protects nn->nfsd_serv -- both the pointer itself and the members
@@ -98,7 +104,7 @@ static struct svc_program    nfsd_acl_program = {
        .pg_class               = "nfsd",
        .pg_stats               = &nfsd_acl_svcstats,
        .pg_authenticate        = &svc_set_client,
-       .pg_init_request        = svc_generic_init_request,
+       .pg_init_request        = nfsd_acl_init_request,
        .pg_rpcbind_set         = nfsd_acl_rpcbind_set,
 };
 
@@ -119,7 +125,6 @@ static const struct svc_version *nfsd_version[] = {
 
 #define NFSD_MINVERS           2
 #define NFSD_NRVERS            ARRAY_SIZE(nfsd_version)
-static const struct svc_version *nfsd_versions[NFSD_NRVERS];
 
 struct svc_program             nfsd_program = {
 #if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
@@ -127,78 +132,136 @@ struct svc_program               nfsd_program = {
 #endif
        .pg_prog                = NFS_PROGRAM,          /* program number */
        .pg_nvers               = NFSD_NRVERS,          /* nr of entries in nfsd_version */
-       .pg_vers                = nfsd_versions,        /* version table */
+       .pg_vers                = nfsd_version        /* version table */
        .pg_name                = "nfsd",               /* program name */
        .pg_class               = "nfsd",               /* authentication class */
        .pg_stats               = &nfsd_svcstats,       /* version table */
        .pg_authenticate        = &svc_set_client,      /* export authentication */
-       .pg_init_request        = svc_generic_init_request,
+       .pg_init_request        = nfsd_init_request,
        .pg_rpcbind_set         = nfsd_rpcbind_set,
 };
 
-static bool nfsd_supported_minorversions[NFSD_SUPPORTED_MINOR_VERSION + 1] = {
-       [0] = 1,
-       [1] = 1,
-       [2] = 1,
-};
+static bool
+nfsd_support_version(int vers)
+{
+       if (vers >= NFSD_MINVERS && vers < NFSD_NRVERS)
+               return nfsd_version[vers] != NULL;
+       return false;
+}
+
+static bool *
+nfsd_alloc_versions(void)
+{
+       bool *vers = kmalloc_array(NFSD_NRVERS, sizeof(bool), GFP_KERNEL);
+       unsigned i;
+
+       if (vers) {
+               /* All compiled versions are enabled by default */
+               for (i = 0; i < NFSD_NRVERS; i++)
+                       vers[i] = nfsd_support_version(i);
+       }
+       return vers;
+}
+
+static bool *
+nfsd_alloc_minorversions(void)
+{
+       bool *vers = kmalloc_array(NFSD_SUPPORTED_MINOR_VERSION + 1,
+                       sizeof(bool), GFP_KERNEL);
+       unsigned i;
 
-int nfsd_vers(int vers, enum vers_op change)
+       if (vers) {
+               /* All minor versions are enabled by default */
+               for (i = 0; i <= NFSD_SUPPORTED_MINOR_VERSION; i++)
+                       vers[i] = nfsd_support_version(4);
+       }
+       return vers;
+}
+
+void
+nfsd_netns_free_versions(struct nfsd_net *nn)
+{
+       kfree(nn->nfsd_versions);
+       kfree(nn->nfsd4_minorversions);
+       nn->nfsd_versions = NULL;
+       nn->nfsd4_minorversions = NULL;
+}
+
+static void
+nfsd_netns_init_versions(struct nfsd_net *nn)
+{
+       if (!nn->nfsd_versions) {
+               nn->nfsd_versions = nfsd_alloc_versions();
+               nn->nfsd4_minorversions = nfsd_alloc_minorversions();
+               if (!nn->nfsd_versions || !nn->nfsd4_minorversions)
+                       nfsd_netns_free_versions(nn);
+       }
+}
+
+int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change)
 {
        if (vers < NFSD_MINVERS || vers >= NFSD_NRVERS)
                return 0;
        switch(change) {
        case NFSD_SET:
-               nfsd_versions[vers] = nfsd_version[vers];
-#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
-               if (vers < NFSD_ACL_NRVERS)
-                       nfsd_acl_versions[vers] = nfsd_acl_version[vers];
-#endif
+               if (nn->nfsd_versions)
+                       nn->nfsd_versions[vers] = nfsd_support_version(vers);
                break;
        case NFSD_CLEAR:
-               nfsd_versions[vers] = NULL;
-#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
-               if (vers < NFSD_ACL_NRVERS)
-                       nfsd_acl_versions[vers] = NULL;
-#endif
+               nfsd_netns_init_versions(nn);
+               if (nn->nfsd_versions)
+                       nn->nfsd_versions[vers] = false;
                break;
        case NFSD_TEST:
-               return nfsd_versions[vers] != NULL;
+               if (nn->nfsd_versions)
+                       return nn->nfsd_versions[vers];
+               /* Fallthrough */
        case NFSD_AVAIL:
-               return nfsd_version[vers] != NULL;
+               return nfsd_support_version(vers);
        }
        return 0;
 }
 
 static void
-nfsd_adjust_nfsd_versions4(void)
+nfsd_adjust_nfsd_versions4(struct nfsd_net *nn)
 {
        unsigned i;
 
        for (i = 0; i <= NFSD_SUPPORTED_MINOR_VERSION; i++) {
-               if (nfsd_supported_minorversions[i])
+               if (nn->nfsd4_minorversions[i])
                        return;
        }
-       nfsd_vers(4, NFSD_CLEAR);
+       nfsd_vers(nn, 4, NFSD_CLEAR);
 }
 
-int nfsd_minorversion(u32 minorversion, enum vers_op change)
+int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change)
 {
        if (minorversion > NFSD_SUPPORTED_MINOR_VERSION &&
            change != NFSD_AVAIL)
                return -1;
+
        switch(change) {
        case NFSD_SET:
-               nfsd_supported_minorversions[minorversion] = true;
-               nfsd_vers(4, NFSD_SET);
+               if (nn->nfsd4_minorversions) {
+                       nfsd_vers(nn, 4, NFSD_SET);
+                       nn->nfsd4_minorversions[minorversion] =
+                               nfsd_vers(nn, 4, NFSD_TEST);
+               }
                break;
        case NFSD_CLEAR:
-               nfsd_supported_minorversions[minorversion] = false;
-               nfsd_adjust_nfsd_versions4();
+               nfsd_netns_init_versions(nn);
+               if (nn->nfsd4_minorversions) {
+                       nn->nfsd4_minorversions[minorversion] = false;
+                       nfsd_adjust_nfsd_versions4(nn);
+               }
                break;
        case NFSD_TEST:
-               return nfsd_supported_minorversions[minorversion];
+               if (nn->nfsd4_minorversions)
+                       return nn->nfsd4_minorversions[minorversion];
+               return nfsd_vers(nn, 4, NFSD_TEST);
        case NFSD_AVAIL:
-               return minorversion <= NFSD_SUPPORTED_MINOR_VERSION;
+               return minorversion <= NFSD_SUPPORTED_MINOR_VERSION &&
+                       nfsd_vers(nn, 4, NFSD_AVAIL);
        }
        return 0;
 }
@@ -280,13 +343,9 @@ static void nfsd_shutdown_generic(void)
        nfsd_racache_shutdown();
 }
 
-static bool nfsd_needs_lockd(void)
+static bool nfsd_needs_lockd(struct nfsd_net *nn)
 {
-#if defined(CONFIG_NFSD_V3)
-       return (nfsd_versions[2] != NULL) || (nfsd_versions[3] != NULL);
-#else
-       return (nfsd_versions[2] != NULL);
-#endif
+       return nfsd_vers(nn, 2, NFSD_TEST) || nfsd_vers(nn, 3, NFSD_TEST);
 }
 
 static int nfsd_startup_net(int nrservs, struct net *net)
@@ -304,7 +363,7 @@ static int nfsd_startup_net(int nrservs, struct net *net)
        if (ret)
                goto out_socks;
 
-       if (nfsd_needs_lockd() && !nn->lockd_up) {
+       if (nfsd_needs_lockd(nn) && !nn->lockd_up) {
                ret = lockd_up(net);
                if (ret)
                        goto out_socks;
@@ -437,20 +496,20 @@ static void nfsd_last_thread(struct svc_serv *serv, struct net *net)
        nfsd_export_flush(net);
 }
 
-void nfsd_reset_versions(void)
+void nfsd_reset_versions(struct nfsd_net *nn)
 {
        int i;
 
        for (i = 0; i < NFSD_NRVERS; i++)
-               if (nfsd_vers(i, NFSD_TEST))
+               if (nfsd_vers(nn, i, NFSD_TEST))
                        return;
 
        for (i = 0; i < NFSD_NRVERS; i++)
                if (i != 4)
-                       nfsd_vers(i, NFSD_SET);
+                       nfsd_vers(nn, i, NFSD_SET);
                else {
                        int minor = 0;
-                       while (nfsd_minorversion(minor, NFSD_SET) >= 0)
+                       while (nfsd_minorversion(nn, minor, NFSD_SET) >= 0)
                                minor++;
                }
 }
@@ -518,7 +577,7 @@ int nfsd_create_serv(struct net *net)
        }
        if (nfsd_max_blksize == 0)
                nfsd_max_blksize = nfsd_get_default_max_blksize();
-       nfsd_reset_versions();
+       nfsd_reset_versions(nn);
        nn->nfsd_serv = svc_create_pooled(&nfsd_program, nfsd_max_blksize,
                                                &nfsd_thread_sv_ops);
        if (nn->nfsd_serv == NULL)
@@ -697,11 +756,44 @@ nfsd_acl_rpcbind_set(struct net *net, const struct svc_program *progp,
                     unsigned short port)
 {
        if (!nfsd_support_acl_version(version) ||
-           !nfsd_vers(version, NFSD_TEST))
+           !nfsd_vers(net_generic(net, nfsd_net_id), version, NFSD_TEST))
                return 0;
        return svc_generic_rpcbind_set(net, progp, version, family,
                        proto, port);
 }
+
+static __be32
+nfsd_acl_init_request(struct svc_rqst *rqstp,
+                     const struct svc_program *progp,
+                     struct svc_process_info *ret)
+{
+       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+       int i;
+
+       if (likely(nfsd_support_acl_version(rqstp->rq_vers) &&
+           nfsd_vers(nn, rqstp->rq_vers, NFSD_TEST)))
+               return svc_generic_init_request(rqstp, progp, ret);
+
+       ret->mismatch.lovers = NFSD_ACL_NRVERS;
+       for (i = NFSD_ACL_MINVERS; i < NFSD_ACL_NRVERS; i++) {
+               if (nfsd_support_acl_version(rqstp->rq_vers) &&
+                   nfsd_vers(nn, i, NFSD_TEST)) {
+                       ret->mismatch.lovers = i;
+                       break;
+               }
+       }
+       if (ret->mismatch.lovers == NFSD_ACL_NRVERS)
+               return rpc_prog_unavail;
+       ret->mismatch.hivers = NFSD_ACL_MINVERS;
+       for (i = NFSD_ACL_NRVERS - 1; i >= NFSD_ACL_MINVERS; i--) {
+               if (nfsd_support_acl_version(rqstp->rq_vers) &&
+                   nfsd_vers(nn, i, NFSD_TEST)) {
+                       ret->mismatch.hivers = i;
+                       break;
+               }
+       }
+       return rpc_prog_mismatch;
+}
 #endif
 
 static int
@@ -709,12 +801,42 @@ nfsd_rpcbind_set(struct net *net, const struct svc_program *progp,
                 u32 version, int family, unsigned short proto,
                 unsigned short port)
 {
-       if (!nfsd_vers(version, NFSD_TEST))
+       if (!nfsd_vers(net_generic(net, nfsd_net_id), version, NFSD_TEST))
                return 0;
        return svc_generic_rpcbind_set(net, progp, version, family,
                        proto, port);
 }
 
+static __be32
+nfsd_init_request(struct svc_rqst *rqstp,
+                 const struct svc_program *progp,
+                 struct svc_process_info *ret)
+{
+       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+       int i;
+
+       if (likely(nfsd_vers(nn, rqstp->rq_vers, NFSD_TEST)))
+               return svc_generic_init_request(rqstp, progp, ret);
+
+       ret->mismatch.lovers = NFSD_NRVERS;
+       for (i = NFSD_MINVERS; i < NFSD_NRVERS; i++) {
+               if (nfsd_vers(nn, i, NFSD_TEST)) {
+                       ret->mismatch.lovers = i;
+                       break;
+               }
+       }
+       if (ret->mismatch.lovers == NFSD_NRVERS)
+               return rpc_prog_unavail;
+       ret->mismatch.hivers = NFSD_MINVERS;
+       for (i = NFSD_NRVERS - 1; i >= NFSD_MINVERS; i--) {
+               if (nfsd_vers(nn, i, NFSD_TEST)) {
+                       ret->mismatch.hivers = i;
+                       break;
+               }
+       }
+       return rpc_prog_mismatch;
+}
+
 /*
  * This is the NFS server kernel thread
  */