nfsd: Allow containers to set supported nfs versions
authorTrond Myklebust <trondmy@gmail.com>
Tue, 9 Apr 2019 15:46:19 +0000 (11:46 -0400)
committerJ. Bruce Fields <bfields@redhat.com>
Wed, 24 Apr 2019 13:46:35 +0000 (09:46 -0400)
Support use of the --nfs-version/--no-nfs-version arguments to rpc.nfsd
in containers.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
fs/nfsd/netns.h
fs/nfsd/nfs4proc.c
fs/nfsd/nfsctl.c
fs/nfsd/nfsd.h
fs/nfsd/nfssvc.c

index b42aaa2..789abc4 100644 (file)
@@ -134,10 +134,18 @@ struct nfsd_net {
        u32             s2s_cp_cl_id;
        struct idr      s2s_cp_stateids;
        spinlock_t      s2s_cp_lock;
+
+       /*
+        * Version information
+        */
+       bool *nfsd_versions;
+       bool *nfsd4_minorversions;
 };
 
 /* Simple check to find out if a given net was properly initialized */
 #define nfsd_netns_ready(nn) ((nn)->sessionid_hashtbl)
 
+extern void nfsd_netns_free_versions(struct nfsd_net *nn);
+
 extern unsigned int nfsd_net_id;
 #endif /* __NFSD_NETNS_H__ */
index 0cfd257..b0ad72d 100644 (file)
@@ -1926,6 +1926,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
        struct nfsd4_compound_state *cstate = &resp->cstate;
        struct svc_fh *current_fh = &cstate->current_fh;
        struct svc_fh *save_fh = &cstate->save_fh;
+       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
        __be32          status;
 
        svcxdr_init_encode(rqstp, resp);
@@ -1948,7 +1949,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
         * According to RFC3010, this takes precedence over all other errors.
         */
        status = nfserr_minor_vers_mismatch;
-       if (nfsd_minorversion(args->minorversion, NFSD_TEST) <= 0)
+       if (nfsd_minorversion(nn, args->minorversion, NFSD_TEST) <= 0)
                goto out;
        status = nfserr_resource;
        if (args->opcnt > NFSD_MAX_OPS_PER_COMPOUND)
index 6667d3a..630d629 100644 (file)
@@ -537,14 +537,14 @@ out_free:
 }
 
 static ssize_t
-nfsd_print_version_support(char *buf, int remaining, const char *sep,
-               unsigned vers, int minor)
+nfsd_print_version_support(struct nfsd_net *nn, char *buf, int remaining,
+               const char *sep, unsigned vers, int minor)
 {
        const char *format = minor < 0 ? "%s%c%u" : "%s%c%u.%u";
-       bool supported = !!nfsd_vers(vers, NFSD_TEST);
+       bool supported = !!nfsd_vers(nn, vers, NFSD_TEST);
 
        if (vers == 4 && minor >= 0 &&
-           !nfsd_minorversion(minor, NFSD_TEST))
+           !nfsd_minorversion(nn, minor, NFSD_TEST))
                supported = false;
        if (minor == 0 && supported)
                /*
@@ -599,20 +599,20 @@ static ssize_t __write_versions(struct file *file, char *buf, size_t size)
                        switch(num) {
                        case 2:
                        case 3:
-                               nfsd_vers(num, cmd);
+                               nfsd_vers(nn, num, cmd);
                                break;
                        case 4:
                                if (*minorp == '.') {
-                                       if (nfsd_minorversion(minor, cmd) < 0)
+                                       if (nfsd_minorversion(nn, minor, cmd) < 0)
                                                return -EINVAL;
-                               } else if ((cmd == NFSD_SET) != nfsd_vers(num, NFSD_TEST)) {
+                               } else if ((cmd == NFSD_SET) != nfsd_vers(nn, num, NFSD_TEST)) {
                                        /*
                                         * Either we have +4 and no minors are enabled,
                                         * or we have -4 and at least one minor is enabled.
                                         * In either case, propagate 'cmd' to all minors.
                                         */
                                        minor = 0;
-                                       while (nfsd_minorversion(minor, cmd) >= 0)
+                                       while (nfsd_minorversion(nn, minor, cmd) >= 0)
                                                minor++;
                                }
                                break;
@@ -624,7 +624,7 @@ static ssize_t __write_versions(struct file *file, char *buf, size_t size)
                /* If all get turned off, turn them back on, as
                 * having no versions is BAD
                 */
-               nfsd_reset_versions();
+               nfsd_reset_versions(nn);
        }
 
        /* Now write current state into reply buffer */
@@ -633,12 +633,12 @@ static ssize_t __write_versions(struct file *file, char *buf, size_t size)
        remaining = SIMPLE_TRANSACTION_LIMIT;
        for (num=2 ; num <= 4 ; num++) {
                int minor;
-               if (!nfsd_vers(num, NFSD_AVAIL))
+               if (!nfsd_vers(nn, num, NFSD_AVAIL))
                        continue;
 
                minor = -1;
                do {
-                       len = nfsd_print_version_support(buf, remaining,
+                       len = nfsd_print_version_support(nn, buf, remaining,
                                        sep, num, minor);
                        if (len >= remaining)
                                goto out;
@@ -1239,6 +1239,8 @@ static __net_init int nfsd_init_net(struct net *net)
        retval = nfsd_idmap_init(net);
        if (retval)
                goto out_idmap_error;
+       nn->nfsd_versions = NULL;
+       nn->nfsd4_minorversions = NULL;
        nn->nfsd4_lease = 90;   /* default lease time */
        nn->nfsd4_grace = 90;
        nn->somebody_reclaimed = false;
@@ -1261,6 +1263,7 @@ static __net_exit void nfsd_exit_net(struct net *net)
 {
        nfsd_idmap_shutdown(net);
        nfsd_export_shutdown(net);
+       nfsd_netns_free_versions(net_generic(net, nfsd_net_id));
 }
 
 static struct pernet_operations nfsd_net_ops = {
index 0668999..6bae255 100644 (file)
@@ -98,10 +98,12 @@ extern const struct svc_version nfsd_acl_version3;
 #endif
 #endif
 
+struct nfsd_net;
+
 enum vers_op {NFSD_SET, NFSD_CLEAR, NFSD_TEST, NFSD_AVAIL };
-int nfsd_vers(int vers, enum vers_op change);
-int nfsd_minorversion(u32 minorversion, enum vers_op change);
-void nfsd_reset_versions(void);
+int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change);
+int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change);
+void nfsd_reset_versions(struct nfsd_net *nn);
 int nfsd_create_serv(struct net *net);
 
 extern int nfsd_max_blksize;
index 378edcf..5207577 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
  */