NFS: Allow the "nfs" file system type to support NFSv4
[pandora-kernel.git] / fs / nfs / super.c
index 9c85cdb..34b1ccf 100644 (file)
@@ -73,7 +73,7 @@ enum {
        Opt_cto, Opt_nocto,
        Opt_ac, Opt_noac,
        Opt_lock, Opt_nolock,
-       Opt_v2, Opt_v3,
+       Opt_v2, Opt_v3, Opt_v4,
        Opt_udp, Opt_tcp, Opt_rdma,
        Opt_acl, Opt_noacl,
        Opt_rdirplus, Opt_nordirplus,
@@ -127,6 +127,7 @@ static const match_table_t nfs_mount_option_tokens = {
        { Opt_nolock, "nolock" },
        { Opt_v2, "v2" },
        { Opt_v3, "v3" },
+       { Opt_v4, "v4" },
        { Opt_udp, "udp" },
        { Opt_tcp, "tcp" },
        { Opt_rdma, "rdma" },
@@ -272,6 +273,10 @@ static const struct super_operations nfs_sops = {
 };
 
 #ifdef CONFIG_NFS_V4
+static int nfs4_validate_text_mount_data(void *options,
+       struct nfs_parsed_mount_data *args, const char *dev_name);
+static int nfs4_try_mount(int flags, const char *dev_name,
+       struct nfs_parsed_mount_data *data, struct vfsmount *mnt);
 static int nfs4_get_sb(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt);
 static int nfs4_remote_get_sb(struct file_system_type *fs_type,
@@ -746,6 +751,21 @@ static int nfs_verify_server_address(struct sockaddr *addr)
        return 0;
 }
 
+/*
+ * Select between a default port value and a user-specified port value.
+ * If a zero value is set, then autobind will be used.
+ */
+static void nfs_set_default_port(struct sockaddr *sap, const int parsed_port,
+                                const unsigned short default_port)
+{
+       unsigned short port = default_port;
+
+       if (parsed_port != NFS_UNSPEC_PORT)
+               port = parsed_port;
+
+       rpc_set_port(sap, port);
+}
+
 /*
  * Sanity check the NFS transport protocol.
  *
@@ -915,10 +935,18 @@ static int nfs_parse_mount_options(char *raw,
                        break;
                case Opt_v2:
                        mnt->flags &= ~NFS_MOUNT_VER3;
+                       mnt->version = 2;
                        break;
                case Opt_v3:
                        mnt->flags |= NFS_MOUNT_VER3;
+                       mnt->version = 3;
                        break;
+#ifdef CONFIG_NFS_V4
+               case Opt_v4:
+                       mnt->flags &= ~NFS_MOUNT_VER3;
+                       mnt->version = 4;
+                       break;
+#endif
                case Opt_udp:
                        mnt->flags &= ~NFS_MOUNT_TCP;
                        mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
@@ -1132,10 +1160,18 @@ static int nfs_parse_mount_options(char *raw,
                        switch (option) {
                        case NFS2_VERSION:
                                mnt->flags &= ~NFS_MOUNT_VER3;
+                               mnt->version = 2;
                                break;
                        case NFS3_VERSION:
                                mnt->flags |= NFS_MOUNT_VER3;
+                               mnt->version = 3;
+                               break;
+#ifdef CONFIG_NFS_V4
+                       case NFS4_VERSION:
+                               mnt->flags &= ~NFS_MOUNT_VER3;
+                               mnt->version = 4;
                                break;
+#endif
                        default:
                                goto out_invalid_value;
                        }
@@ -1337,6 +1373,16 @@ static int nfs_walk_authlist(struct nfs_parsed_mount_data *args,
 {
        unsigned int i, j, server_authlist_len = *(request->auth_flav_len);
 
+       /*
+        * Certain releases of Linux's mountd return an empty
+        * flavor list.  To prevent behavioral regression with
+        * these servers (ie. rejecting mounts that used to
+        * succeed), revert to pre-2.6.32 behavior (no checking)
+        * if the returned flavor list is empty.
+        */
+       if (server_authlist_len == 0)
+               return 0;
+
        /*
         * We avoid sophisticated negotiating here, as there are
         * plenty of cases where we can get it wrong, providing
@@ -1405,11 +1451,7 @@ static int nfs_try_mount(struct nfs_parsed_mount_data *args,
                args->mount_server.addrlen = args->nfs_server.addrlen;
        }
        request.salen = args->mount_server.addrlen;
-
-       /*
-        * autobind will be used if mount_server.port == 0
-        */
-       rpc_set_port(request.sap, args->mount_server.port);
+       nfs_set_default_port(request.sap, args->mount_server.port, 0);
 
        /*
         * Now ask the mount server to map our export path
@@ -1587,6 +1629,7 @@ static int nfs_validate_mount_data(void *options,
                                   const char *dev_name)
 {
        struct nfs_mount_data *data = (struct nfs_mount_data *)options;
+       struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
 
        if (data == NULL)
                goto out_no_data;
@@ -1598,11 +1641,12 @@ static int nfs_validate_mount_data(void *options,
        args->acregmax          = NFS_DEF_ACREGMAX;
        args->acdirmin          = NFS_DEF_ACDIRMIN;
        args->acdirmax          = NFS_DEF_ACDIRMAX;
-       args->mount_server.port = 0;    /* autobind unless user sets port */
-       args->nfs_server.port   = 0;    /* autobind unless user sets port */
+       args->mount_server.port = NFS_UNSPEC_PORT;
+       args->nfs_server.port   = NFS_UNSPEC_PORT;
        args->nfs_server.protocol = XPRT_TRANSPORT_TCP;
        args->auth_flavors[0]   = RPC_AUTH_UNIX;
        args->auth_flavor_len   = 1;
+       args->minorversion      = 0;
 
        switch (data->version) {
        case 1:
@@ -1624,8 +1668,11 @@ static int nfs_validate_mount_data(void *options,
                        if (data->root.size > NFS3_FHSIZE || data->root.size == 0)
                                goto out_invalid_fh;
                        mntfh->size = data->root.size;
-               } else
+                       args->version = 3;
+               } else {
                        mntfh->size = NFS2_FHSIZE;
+                       args->version = 2;
+               }
 
 
                memcpy(mntfh->data, data->root.data, mntfh->size);
@@ -1647,11 +1694,9 @@ static int nfs_validate_mount_data(void *options,
                args->acdirmin          = data->acdirmin;
                args->acdirmax          = data->acdirmax;
 
-               memcpy(&args->nfs_server.address, &data->addr,
-                      sizeof(data->addr));
+               memcpy(sap, &data->addr, sizeof(data->addr));
                args->nfs_server.addrlen = sizeof(data->addr);
-               if (!nfs_verify_server_address((struct sockaddr *)
-                                               &args->nfs_server.address))
+               if (!nfs_verify_server_address(sap))
                        goto out_no_address;
 
                if (!(data->flags & NFS_MOUNT_TCP))
@@ -1699,12 +1744,18 @@ static int nfs_validate_mount_data(void *options,
                if (nfs_parse_mount_options((char *)options, args) == 0)
                        return -EINVAL;
 
-               if (!nfs_verify_server_address((struct sockaddr *)
-                                               &args->nfs_server.address))
+               if (!nfs_verify_server_address(sap))
                        goto out_no_address;
 
-               rpc_set_port((struct sockaddr *)&args->nfs_server.address,
-                               args->nfs_server.port);
+               if (args->version == 4)
+#ifdef CONFIG_NFS_V4
+                       return nfs4_validate_text_mount_data(options,
+                                                            args, dev_name);
+#else
+                       goto out_v4_not_compiled;
+#endif
+
+               nfs_set_default_port(sap, args->nfs_server.port, 0);
 
                nfs_set_mount_transport_protocol(args);
 
@@ -1752,6 +1803,12 @@ out_v3_not_compiled:
        return -EPROTONOSUPPORT;
 #endif /* !CONFIG_NFS_V3 */
 
+#ifndef CONFIG_NFS_V4
+out_v4_not_compiled:
+       dfprintk(MOUNT, "NFS: NFSv4 is not compiled into kernel\n");
+       return -EPROTONOSUPPORT;
+#endif /* !CONFIG_NFS_V4 */
+
 out_nomem:
        dfprintk(MOUNT, "NFS: not enough memory to handle mount options\n");
        return -ENOMEM;
@@ -2047,6 +2104,14 @@ static int nfs_get_sb(struct file_system_type *fs_type,
        if (error < 0)
                goto out;
 
+#ifdef CONFIG_NFS_V4
+       if (data->version == 4) {
+               error = nfs4_try_mount(flags, dev_name, data, mnt);
+               kfree(data->client_address);
+               goto out;
+       }
+#endif /* CONFIG_NFS_V4 */
+
        /* Get a volume representation */
        server = nfs_create_server(data, mntfh);
        if (IS_ERR(server)) {
@@ -2244,6 +2309,37 @@ static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)
        args->flags &= ~(NFS_MOUNT_NONLM|NFS_MOUNT_NOACL|NFS_MOUNT_VER3);
 }
 
+static int nfs4_validate_text_mount_data(void *options,
+                                        struct nfs_parsed_mount_data *args,
+                                        const char *dev_name)
+{
+       struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+
+       nfs_set_default_port(sap, args->nfs_server.port, NFS_PORT);
+
+       nfs_validate_transport_protocol(args);
+
+       nfs4_validate_mount_flags(args);
+
+       if (args->auth_flavor_len > 1) {
+               dfprintk(MOUNT,
+                        "NFS4: Too many RPC auth flavours specified\n");
+               return -EINVAL;
+       }
+
+       if (args->client_address == NULL) {
+               dfprintk(MOUNT,
+                        "NFS4: mount program didn't pass callback address\n");
+               return -EINVAL;
+       }
+
+       return nfs_parse_devname(dev_name,
+                                  &args->nfs_server.hostname,
+                                  NFS4_MAXNAMLEN,
+                                  &args->nfs_server.export_path,
+                                  NFS4_MAXPATHLEN);
+}
+
 /*
  * Validate NFSv4 mount options
  */
@@ -2251,7 +2347,7 @@ static int nfs4_validate_mount_data(void *options,
                                    struct nfs_parsed_mount_data *args,
                                    const char *dev_name)
 {
-       struct sockaddr_in *ap;
+       struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
        struct nfs4_mount_data *data = (struct nfs4_mount_data *)options;
        char *c;
 
@@ -2264,23 +2360,22 @@ static int nfs4_validate_mount_data(void *options,
        args->acregmax          = NFS_DEF_ACREGMAX;
        args->acdirmin          = NFS_DEF_ACDIRMIN;
        args->acdirmax          = NFS_DEF_ACDIRMAX;
-       args->nfs_server.port   = NFS_PORT; /* 2049 unless user set port= */
+       args->nfs_server.port   = NFS_UNSPEC_PORT;
        args->auth_flavors[0]   = RPC_AUTH_UNIX;
        args->auth_flavor_len   = 1;
+       args->version           = 4;
        args->minorversion      = 0;
 
        switch (data->version) {
        case 1:
-               ap = (struct sockaddr_in *)&args->nfs_server.address;
                if (data->host_addrlen > sizeof(args->nfs_server.address))
                        goto out_no_address;
                if (data->host_addrlen == 0)
                        goto out_no_address;
                args->nfs_server.addrlen = data->host_addrlen;
-               if (copy_from_user(ap, data->host_addr, data->host_addrlen))
+               if (copy_from_user(sap, data->host_addr, data->host_addrlen))
                        return -EFAULT;
-               if (!nfs_verify_server_address((struct sockaddr *)
-                                               &args->nfs_server.address))
+               if (!nfs_verify_server_address(sap))
                        goto out_no_address;
 
                if (data->auth_flavourlen) {
@@ -2326,39 +2421,14 @@ static int nfs4_validate_mount_data(void *options,
                nfs_validate_transport_protocol(args);
 
                break;
-       default: {
-               int status;
-
+       default:
                if (nfs_parse_mount_options((char *)options, args) == 0)
                        return -EINVAL;
 
-               if (!nfs_verify_server_address((struct sockaddr *)
-                                               &args->nfs_server.address))
+               if (!nfs_verify_server_address(sap))
                        return -EINVAL;
 
-               rpc_set_port((struct sockaddr *)&args->nfs_server.address,
-                               args->nfs_server.port);
-
-               nfs_validate_transport_protocol(args);
-
-               nfs4_validate_mount_flags(args);
-
-               if (args->auth_flavor_len > 1)
-                       goto out_inval_auth;
-
-               if (args->client_address == NULL)
-                       goto out_no_client_address;
-
-               status = nfs_parse_devname(dev_name,
-                                          &args->nfs_server.hostname,
-                                          NFS4_MAXNAMLEN,
-                                          &args->nfs_server.export_path,
-                                          NFS4_MAXPATHLEN);
-               if (status < 0)
-                       return status;
-
-               break;
-               }
+               return nfs4_validate_text_mount_data(options, args, dev_name);
        }
 
        return 0;
@@ -2375,10 +2445,6 @@ out_inval_auth:
 out_no_address:
        dfprintk(MOUNT, "NFS4: mount program didn't pass remote address\n");
        return -EINVAL;
-
-out_no_client_address:
-       dfprintk(MOUNT, "NFS4: mount program didn't pass callback address\n");
-       return -EINVAL;
 }
 
 /*
@@ -2545,6 +2611,34 @@ out_err:
        return ret;
 }
 
+static int nfs4_try_mount(int flags, const char *dev_name,
+                        struct nfs_parsed_mount_data *data,
+                        struct vfsmount *mnt)
+{
+       char *export_path;
+       struct vfsmount *root_mnt;
+       int error;
+
+       dfprintk(MOUNT, "--> nfs4_try_mount()\n");
+
+       export_path = data->nfs_server.export_path;
+       data->nfs_server.export_path = "/";
+       root_mnt = nfs_do_root_mount(&nfs4_remote_fs_type, flags, data,
+                       data->nfs_server.hostname);
+       data->nfs_server.export_path = export_path;
+
+       error = PTR_ERR(root_mnt);
+       if (IS_ERR(root_mnt))
+               goto out;
+
+       error = nfs_follow_remote_path(root_mnt, export_path, mnt);
+
+out:
+       dfprintk(MOUNT, "<-- nfs4_try_mount() = %d%s\n", error,
+                       error != 0 ? " [error]" : "");
+       return error;
+}
+
 /*
  * Get the superblock for an NFS4 mountpoint
  */
@@ -2552,8 +2646,6 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt)
 {
        struct nfs_parsed_mount_data *data;
-       char *export_path;
-       struct vfsmount *root_mnt;
        int error = -ENOMEM;
 
        data = kzalloc(sizeof(*data), GFP_KERNEL);
@@ -2565,17 +2657,7 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
        if (error < 0)
                goto out;
 
-       export_path = data->nfs_server.export_path;
-       data->nfs_server.export_path = "/";
-       root_mnt = nfs_do_root_mount(&nfs4_remote_fs_type, flags, data,
-                       data->nfs_server.hostname);
-       data->nfs_server.export_path = export_path;
-
-       error = PTR_ERR(root_mnt);
-       if (IS_ERR(root_mnt))
-               goto out;
-
-       error = nfs_follow_remote_path(root_mnt, export_path, mnt);
+       error = nfs4_try_mount(flags, dev_name, data, mnt);
 
 out:
        kfree(data->client_address);