]> err.no Git - linux-2.6/blobdiff - fs/nfs/super.c
NFSv4: Don't call put_rpccred() from an rcu callback
[linux-2.6] / fs / nfs / super.c
index 48db52a7067a626e96aad2a06249f661671823e9..b2a851c1b8cb0230a59a2dfef0a2dd77f9c9593d 100644 (file)
@@ -100,6 +100,7 @@ enum {
        Opt_udp, Opt_tcp,
        Opt_acl, Opt_noacl,
        Opt_rdirplus, Opt_nordirplus,
+       Opt_sharecache, Opt_nosharecache,
 
        /* Mount options that take integer arguments */
        Opt_port,
@@ -146,6 +147,8 @@ static match_table_t nfs_mount_option_tokens = {
        { Opt_noacl, "noacl" },
        { Opt_rdirplus, "rdirplus" },
        { Opt_nordirplus, "nordirplus" },
+       { Opt_sharecache, "sharecache" },
+       { Opt_nosharecache, "nosharecache" },
 
        { Opt_port, "port=%u" },
        { Opt_rsize, "rsize=%u" },
@@ -297,7 +300,10 @@ static const struct super_operations nfs4_sops = {
 };
 #endif
 
-static struct shrinker *acl_shrinker;
+static struct shrinker acl_shrinker = {
+       .shrink         = nfs_access_cache_shrinker,
+       .seeks          = DEFAULT_SEEKS,
+};
 
 /*
  * Register the NFS filesystems
@@ -318,7 +324,7 @@ int __init register_nfs_fs(void)
        if (ret < 0)
                goto error_2;
 #endif
-       acl_shrinker = set_shrinker(DEFAULT_SEEKS, nfs_access_cache_shrinker);
+       register_shrinker(&acl_shrinker);
        return 0;
 
 #ifdef CONFIG_NFS_V4
@@ -336,8 +342,7 @@ error_0:
  */
 void __exit unregister_nfs_fs(void)
 {
-       if (acl_shrinker != NULL)
-               remove_shrinker(acl_shrinker);
+       unregister_shrinker(&acl_shrinker);
 #ifdef CONFIG_NFS_V4
        unregister_filesystem(&nfs4_fs_type);
        nfs_unregister_sysctl();
@@ -450,6 +455,7 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,
                { NFS_MOUNT_NONLM, ",nolock", "" },
                { NFS_MOUNT_NOACL, ",noacl", "" },
                { NFS_MOUNT_NORDIRPLUS, ",nordirplus", "" },
+               { NFS_MOUNT_UNSHARED, ",nosharecache", ""},
                { 0, NULL, NULL }
        };
        const struct proc_nfs_info *nfs_infop;
@@ -714,13 +720,19 @@ static int nfs_parse_mount_options(char *raw,
                case Opt_nordirplus:
                        mnt->flags |= NFS_MOUNT_NORDIRPLUS;
                        break;
+               case Opt_sharecache:
+                       mnt->flags &= ~NFS_MOUNT_UNSHARED;
+                       break;
+               case Opt_nosharecache:
+                       mnt->flags |= NFS_MOUNT_UNSHARED;
+                       break;
 
                case Opt_port:
                        if (match_int(args, &option))
                                return 0;
                        if (option < 0 || option > 65535)
                                return 0;
-                       mnt->nfs_server.address.sin_port = htonl(option);
+                       mnt->nfs_server.address.sin_port = htons(option);
                        break;
                case Opt_rsize:
                        if (match_int(args, &mnt->rsize))
@@ -1051,10 +1063,28 @@ out_err:
 /*
  * Validate the NFS2/NFS3 mount data
  * - fills in the mount root filehandle
+ *
+ * For option strings, user space handles the following behaviors:
+ *
+ * + DNS: mapping server host name to IP address ("addr=" option)
+ *
+ * + failure mode: how to behave if a mount request can't be handled
+ *   immediately ("fg/bg" option)
+ *
+ * + retry: how often to retry a mount request ("retry=" option)
+ *
+ * + breaking back: trying proto=udp after proto=tcp, v2 after v3,
+ *   mountproto=tcp after mountproto=udp, and so on
+ *
+ * XXX: as far as I can tell, changing the NFS program number is not
+ *      supported in the NFS client.
  */
-static int nfs_validate_mount_data(struct nfs_mount_data *data,
-                                  struct nfs_fh *mntfh)
+static int nfs_validate_mount_data(struct nfs_mount_data **options,
+                                  struct nfs_fh *mntfh,
+                                  const char *dev_name)
 {
+       struct nfs_mount_data *data = *options;
+
        if (data == NULL)
                goto out_no_data;
 
@@ -1087,8 +1117,78 @@ static int nfs_validate_mount_data(struct nfs_mount_data *data,
                        memset(mntfh->data + mntfh->size, 0,
                               sizeof(mntfh->data) - mntfh->size);
                break;
-       default:
-               goto out_bad_version;
+       default: {
+               unsigned int len;
+               char *c;
+               int status;
+               struct nfs_parsed_mount_data args = {
+                       .flags          = (NFS_MOUNT_VER3 | NFS_MOUNT_TCP),
+                       .rsize          = NFS_MAX_FILE_IO_SIZE,
+                       .wsize          = NFS_MAX_FILE_IO_SIZE,
+                       .timeo          = 600,
+                       .retrans        = 2,
+                       .acregmin       = 3,
+                       .acregmax       = 60,
+                       .acdirmin       = 30,
+                       .acdirmax       = 60,
+                       .mount_server.protocol = IPPROTO_UDP,
+                       .mount_server.program = NFS_MNT_PROGRAM,
+                       .nfs_server.protocol = IPPROTO_TCP,
+                       .nfs_server.program = NFS_PROGRAM,
+               };
+
+               if (nfs_parse_mount_options((char *) *options, &args) == 0)
+                       return -EINVAL;
+
+               data = kzalloc(sizeof(*data), GFP_KERNEL);
+               if (data == NULL)
+                       return -ENOMEM;
+
+               /*
+                * NB: after this point, caller will free "data"
+                * if we return an error
+                */
+               *options = data;
+
+               c = strchr(dev_name, ':');
+               if (c == NULL)
+                       return -EINVAL;
+               len = c - dev_name - 1;
+               if (len > sizeof(data->hostname))
+                       return -EINVAL;
+               strncpy(data->hostname, dev_name, len);
+               args.nfs_server.hostname = data->hostname;
+
+               c++;
+               if (strlen(c) > NFS_MAXPATHLEN)
+                       return -EINVAL;
+               args.nfs_server.export_path = c;
+
+               status = nfs_try_mount(&args, mntfh);
+               if (status)
+                       return -EINVAL;
+
+               /*
+                * Translate to nfs_mount_data, which nfs_fill_super
+                * can deal with.
+                */
+               data->version           = 6;
+               data->flags             = args.flags;
+               data->rsize             = args.rsize;
+               data->wsize             = args.wsize;
+               data->timeo             = args.timeo;
+               data->retrans           = args.retrans;
+               data->acregmin          = args.acregmin;
+               data->acregmax          = args.acregmax;
+               data->acdirmin          = args.acdirmin;
+               data->acdirmax          = args.acdirmax;
+               data->addr              = args.nfs_server.address;
+               data->namlen            = args.namlen;
+               data->bsize             = args.bsize;
+               data->pseudoflavor      = args.auth_flavors[0];
+
+               break;
+               }
        }
 
        if (!(data->flags & NFS_MOUNT_SECFLAVOUR))
@@ -1117,11 +1217,6 @@ out_no_sec:
        dfprintk(MOUNT, "NFS: nfs_mount_data version supports only AUTH_SYS\n");
        return -EINVAL;
 
-out_bad_version:
-       dfprintk(MOUNT, "NFS: bad nfs_mount_data version %d\n",
-                data->version);
-       return -EINVAL;
-
 #ifndef CONFIG_NFS_V3
 out_v3_not_compiled:
        dfprintk(MOUNT, "NFS: NFSv3 is not compiled into kernel\n");
@@ -1224,13 +1319,51 @@ static int nfs_compare_super(struct super_block *sb, void *data)
 {
        struct nfs_server *server = data, *old = NFS_SB(sb);
 
-       if (old->nfs_client != server->nfs_client)
+       if (memcmp(&old->nfs_client->cl_addr,
+                               &server->nfs_client->cl_addr,
+                               sizeof(old->nfs_client->cl_addr)) != 0)
+               return 0;
+       /* Note: NFS_MOUNT_UNSHARED == NFS4_MOUNT_UNSHARED */
+       if (old->flags & NFS_MOUNT_UNSHARED)
                return 0;
        if (memcmp(&old->fsid, &server->fsid, sizeof(old->fsid)) != 0)
                return 0;
        return 1;
 }
 
+#define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS)
+
+static int nfs_compare_mount_options(const struct super_block *s, const struct nfs_server *b, int flags)
+{
+       const struct nfs_server *a = s->s_fs_info;
+       const struct rpc_clnt *clnt_a = a->client;
+       const struct rpc_clnt *clnt_b = b->client;
+
+       if ((s->s_flags & NFS_MS_MASK) != (flags & NFS_MS_MASK))
+               goto Ebusy;
+       if (a->nfs_client != b->nfs_client)
+               goto Ebusy;
+       if (a->flags != b->flags)
+               goto Ebusy;
+       if (a->wsize != b->wsize)
+               goto Ebusy;
+       if (a->rsize != b->rsize)
+               goto Ebusy;
+       if (a->acregmin != b->acregmin)
+               goto Ebusy;
+       if (a->acregmax != b->acregmax)
+               goto Ebusy;
+       if (a->acdirmin != b->acdirmin)
+               goto Ebusy;
+       if (a->acdirmax != b->acdirmax)
+               goto Ebusy;
+       if (clnt_a->cl_auth->au_flavor != clnt_b->cl_auth->au_flavor)
+               goto Ebusy;
+       return 0;
+Ebusy:
+       return -EBUSY;
+}
+
 static int nfs_get_sb(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt)
 {
@@ -1239,10 +1372,11 @@ static int nfs_get_sb(struct file_system_type *fs_type,
        struct nfs_fh mntfh;
        struct nfs_mount_data *data = raw_data;
        struct dentry *mntroot;
+       int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
        int error;
 
        /* Validate the mount data */
-       error = nfs_validate_mount_data(data, &mntfh);
+       error = nfs_validate_mount_data(&data, &mntfh, dev_name);
        if (error < 0)
                goto out;
 
@@ -1253,16 +1387,22 @@ static int nfs_get_sb(struct file_system_type *fs_type,
                goto out;
        }
 
+       if (server->flags & NFS_MOUNT_UNSHARED)
+               compare_super = NULL;
+
        /* Get a superblock - note that we may end up sharing one that already exists */
-       s = sget(fs_type, nfs_compare_super, nfs_set_super, server);
+       s = sget(fs_type, compare_super, nfs_set_super, server);
        if (IS_ERR(s)) {
                error = PTR_ERR(s);
                goto out_err_nosb;
        }
 
        if (s->s_fs_info != server) {
+               error = nfs_compare_mount_options(s, server, flags);
                nfs_free_server(server);
                server = NULL;
+               if (error < 0)
+                       goto error_splat_super;
        }
 
        if (!s->s_root) {
@@ -1283,6 +1423,8 @@ static int nfs_get_sb(struct file_system_type *fs_type,
        error = 0;
 
 out:
+       if (data != raw_data)
+               kfree(data);
        return error;
 
 out_err_nosb:
@@ -1317,6 +1459,7 @@ static int nfs_xdev_get_sb(struct file_system_type *fs_type, int flags,
        struct super_block *s;
        struct nfs_server *server;
        struct dentry *mntroot;
+       int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
        int error;
 
        dprintk("--> nfs_xdev_get_sb()\n");
@@ -1328,16 +1471,22 @@ static int nfs_xdev_get_sb(struct file_system_type *fs_type, int flags,
                goto out_err_noserver;
        }
 
+       if (server->flags & NFS_MOUNT_UNSHARED)
+               compare_super = NULL;
+
        /* Get a superblock - note that we may end up sharing one that already exists */
-       s = sget(&nfs_fs_type, nfs_compare_super, nfs_set_super, server);
+       s = sget(&nfs_fs_type, compare_super, nfs_set_super, server);
        if (IS_ERR(s)) {
                error = PTR_ERR(s);
                goto out_err_nosb;
        }
 
        if (s->s_fs_info != server) {
+               error = nfs_compare_mount_options(s, server, flags);
                nfs_free_server(server);
                server = NULL;
+               if (error < 0)
+                       goto error_splat_super;
        }
 
        if (!s->s_root) {
@@ -1456,8 +1605,93 @@ static int nfs4_validate_mount_data(struct nfs4_mount_data **options,
                *ip_addr = c;
 
                break;
-       default:
-               goto out_bad_version;
+       default: {
+               unsigned int len;
+               struct nfs_parsed_mount_data args = {
+                       .rsize          = NFS_MAX_FILE_IO_SIZE,
+                       .wsize          = NFS_MAX_FILE_IO_SIZE,
+                       .timeo          = 600,
+                       .retrans        = 2,
+                       .acregmin       = 3,
+                       .acregmax       = 60,
+                       .acdirmin       = 30,
+                       .acdirmax       = 60,
+                       .nfs_server.protocol = IPPROTO_TCP,
+               };
+
+               if (nfs_parse_mount_options((char *) *options, &args) == 0)
+                       return -EINVAL;
+
+               if (!nfs_verify_server_address((struct sockaddr *)
+                                               &args.nfs_server.address))
+                       return -EINVAL;
+               *addr = args.nfs_server.address;
+
+               switch (args.auth_flavor_len) {
+               case 0:
+                       *authflavour = RPC_AUTH_UNIX;
+                       break;
+               case 1:
+                       *authflavour = (rpc_authflavor_t) args.auth_flavors[0];
+                       break;
+               default:
+                       goto out_inval_auth;
+               }
+
+               /*
+                * Translate to nfs4_mount_data, which nfs4_fill_super
+                * can deal with.
+                */
+               data = kzalloc(sizeof(*data), GFP_KERNEL);
+               if (data == NULL)
+                       return -ENOMEM;
+               *options = data;
+
+               data->version   = 1;
+               data->flags     = args.flags & NFS4_MOUNT_FLAGMASK;
+               data->rsize     = args.rsize;
+               data->wsize     = args.wsize;
+               data->timeo     = args.timeo;
+               data->retrans   = args.retrans;
+               data->acregmin  = args.acregmin;
+               data->acregmax  = args.acregmax;
+               data->acdirmin  = args.acdirmin;
+               data->acdirmax  = args.acdirmax;
+               data->proto     = args.nfs_server.protocol;
+
+               /*
+                * Split "dev_name" into "hostname:mntpath".
+                */
+               c = strchr(dev_name, ':');
+               if (c == NULL)
+                       return -EINVAL;
+               /* while calculating len, pretend ':' is '\0' */
+               len = c - dev_name;
+               if (len > NFS4_MAXNAMLEN)
+                       return -EINVAL;
+               *hostname = kzalloc(len, GFP_KERNEL);
+               if (*hostname == NULL)
+                       return -ENOMEM;
+               strncpy(*hostname, dev_name, len - 1);
+
+               c++;                    /* step over the ':' */
+               len = strlen(c);
+               if (len > NFS4_MAXPATHLEN)
+                       return -EINVAL;
+               *mntpath = kzalloc(len + 1, GFP_KERNEL);
+               if (*mntpath == NULL)
+                       return -ENOMEM;
+               strncpy(*mntpath, c, len);
+
+               dprintk("MNTPATH: %s\n", *mntpath);
+
+               if (args.client_address == NULL)
+                       goto out_no_client_address;
+
+               *ip_addr = args.client_address;
+
+               break;
+               }
        }
 
        return 0;
@@ -1475,9 +1709,8 @@ out_no_address:
        dfprintk(MOUNT, "NFS4: mount program didn't pass remote address\n");
        return -EINVAL;
 
-out_bad_version:
-       dfprintk(MOUNT, "NFS4: bad nfs_mount_data version %d\n",
-                data->version);
+out_no_client_address:
+       dfprintk(MOUNT, "NFS4: mount program didn't pass callback address\n");
        return -EINVAL;
 }
 
@@ -1495,6 +1728,7 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
        struct nfs_fh mntfh;
        struct dentry *mntroot;
        char *mntpath = NULL, *hostname = NULL, *ip_addr = NULL;
+       int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
        int error;
 
        /* Validate the mount data */
@@ -1511,8 +1745,11 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
                goto out;
        }
 
+       if (server->flags & NFS4_MOUNT_UNSHARED)
+               compare_super = NULL;
+
        /* Get a superblock - note that we may end up sharing one that already exists */
-       s = sget(fs_type, nfs_compare_super, nfs_set_super, server);
+       s = sget(fs_type, compare_super, nfs_set_super, server);
        if (IS_ERR(s)) {
                error = PTR_ERR(s);
                goto out_free;
@@ -1578,6 +1815,7 @@ static int nfs4_xdev_get_sb(struct file_system_type *fs_type, int flags,
        struct super_block *s;
        struct nfs_server *server;
        struct dentry *mntroot;
+       int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
        int error;
 
        dprintk("--> nfs4_xdev_get_sb()\n");
@@ -1589,8 +1827,11 @@ static int nfs4_xdev_get_sb(struct file_system_type *fs_type, int flags,
                goto out_err_noserver;
        }
 
+       if (server->flags & NFS4_MOUNT_UNSHARED)
+               compare_super = NULL;
+
        /* Get a superblock - note that we may end up sharing one that already exists */
-       s = sget(&nfs_fs_type, nfs_compare_super, nfs_set_super, server);
+       s = sget(&nfs_fs_type, compare_super, nfs_set_super, server);
        if (IS_ERR(s)) {
                error = PTR_ERR(s);
                goto out_err_nosb;
@@ -1645,6 +1886,7 @@ static int nfs4_referral_get_sb(struct file_system_type *fs_type, int flags,
        struct nfs_server *server;
        struct dentry *mntroot;
        struct nfs_fh mntfh;
+       int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
        int error;
 
        dprintk("--> nfs4_referral_get_sb()\n");
@@ -1656,8 +1898,11 @@ static int nfs4_referral_get_sb(struct file_system_type *fs_type, int flags,
                goto out_err_noserver;
        }
 
+       if (server->flags & NFS4_MOUNT_UNSHARED)
+               compare_super = NULL;
+
        /* Get a superblock - note that we may end up sharing one that already exists */
-       s = sget(&nfs_fs_type, nfs_compare_super, nfs_set_super, server);
+       s = sget(&nfs_fs_type, compare_super, nfs_set_super, server);
        if (IS_ERR(s)) {
                error = PTR_ERR(s);
                goto out_err_nosb;