Skip to content
Snippets Groups Projects
inode.c 77.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	.get_sb		= nfs_clone_nfs_sb,
    	.kill_sb	= nfs_kill_super,
    	.fs_flags	= FS_ODD_RENAME|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_NFS_V4
    
    static void nfs4_clear_inode(struct inode *);
    
    
    static struct super_operations nfs4_sops = { 
    	.alloc_inode	= nfs_alloc_inode,
    	.destroy_inode	= nfs_destroy_inode,
    	.write_inode	= nfs_write_inode,
    	.statfs		= nfs_statfs,
    	.clear_inode	= nfs4_clear_inode,
    	.umount_begin	= nfs_umount_begin,
    	.show_options	= nfs_show_options,
    
    	.show_stats	= nfs_show_stats,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    /*
     * Clean out any remaining NFSv4 state that might be left over due
     * to open() calls that passed nfs_atomic_lookup, but failed to call
     * nfs_open().
     */
    static void nfs4_clear_inode(struct inode *inode)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	/* If we are holding a delegation, return it! */
    
    	nfs_inode_return_delegation(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* First call standard NFS clear_inode() code */
    	nfs_clear_inode(inode);
    	/* Now clear out any remaining state */
    	while (!list_empty(&nfsi->open_states)) {
    		struct nfs4_state *state;
    		
    		state = list_entry(nfsi->open_states.next,
    				struct nfs4_state,
    				inode_states);
    		dprintk("%s(%s/%Ld): found unclaimed NFSv4 state %p\n",
    				__FUNCTION__,
    				inode->i_sb->s_id,
    				(long long)NFS_FILEID(inode),
    				state);
    		BUG_ON(atomic_read(&state->count) != 1);
    		nfs4_close_state(state, state->state);
    	}
    }
    
    
    
    static struct rpc_clnt *nfs4_create_client(struct nfs_server *server,
    	struct rpc_timeout *timeparms, int proto, rpc_authflavor_t flavor)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_client *clp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_xprt *xprt = NULL;
    	struct rpc_clnt *clnt = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	clp = nfs4_get_client(&server->addr.sin_addr);
    	if (!clp) {
    
    		dprintk("%s: failed to create NFS4 client.\n", __FUNCTION__);
    
    		return ERR_PTR(err);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	/* Now create transport and client */
    	down_write(&clp->cl_sem);
    
    	if (IS_ERR(clp->cl_rpcclient)) {
    
    		xprt = xprt_create_proto(proto, &server->addr, timeparms);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (IS_ERR(xprt)) {
    			up_write(&clp->cl_sem);
    			err = PTR_ERR(xprt);
    
    			dprintk("%s: cannot create RPC transport. Error = %d\n",
    					__FUNCTION__, err);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_fail;
    		}
    		clnt = rpc_create_client(xprt, server->hostname, &nfs_program,
    
    				server->rpc_ops->version, flavor);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (IS_ERR(clnt)) {
    			up_write(&clp->cl_sem);
    			err = PTR_ERR(clnt);
    
    			dprintk("%s: cannot create RPC client. Error = %d\n",
    					__FUNCTION__, err);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_fail;
    		}
    		clnt->cl_intr     = 1;
    		clnt->cl_softrtry = 1;
    		clp->cl_rpcclient = clnt;
    		memcpy(clp->cl_ipaddr, server->ip_addr, sizeof(clp->cl_ipaddr));
    		nfs_idmap_new(clp);
    	}
    	list_add_tail(&server->nfs4_siblings, &clp->cl_superblocks);
    	clnt = rpc_clone_client(clp->cl_rpcclient);
    	if (!IS_ERR(clnt))
    
    		server->nfs4_state = clp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	up_write(&clp->cl_sem);
    	clp = NULL;
    
    	if (IS_ERR(clnt)) {
    
    		dprintk("%s: cannot create RPC client. Error = %d\n",
    				__FUNCTION__, err);
    
    		return clnt;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if (server->nfs4_state->cl_idmap == NULL) {
    
    		dprintk("%s: failed to create idmapper.\n", __FUNCTION__);
    
    		return ERR_PTR(-ENOMEM);
    
    	if (clnt->cl_auth->au_flavor != flavor) {
    
    		auth = rpcauth_create(flavor, clnt);
    
    			dprintk("%s: couldn't create credcache!\n", __FUNCTION__);
    
    			return (struct rpc_clnt *)auth;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    	}
    
    	return clnt;
    
     out_fail:
    	if (clp)
    		nfs4_put_client(clp);
    	return ERR_PTR(err);
    }
    
    static int nfs4_fill_super(struct super_block *sb, struct nfs4_mount_data *data, int silent)
    {
    	struct nfs_server *server;
    	struct rpc_timeout timeparms;
    	rpc_authflavor_t authflavour;
    	int err = -EIO;
    
    	sb->s_blocksize_bits = 0;
    	sb->s_blocksize = 0;
    	server = NFS_SB(sb);
    	if (data->rsize != 0)
    		server->rsize = nfs_block_size(data->rsize, NULL);
    	if (data->wsize != 0)
    		server->wsize = nfs_block_size(data->wsize, NULL);
    	server->flags = data->flags & NFS_MOUNT_FLAGMASK;
    	server->caps = NFS_CAP_ATOMIC_OPEN;
    
    	server->acregmin = data->acregmin*HZ;
    	server->acregmax = data->acregmax*HZ;
    	server->acdirmin = data->acdirmin*HZ;
    	server->acdirmax = data->acdirmax*HZ;
    
    	server->rpc_ops = &nfs_v4_clientops;
    
    	nfs_init_timeout_values(&timeparms, data->proto, data->timeo, data->retrans);
    
    	server->retrans_timeo = timeparms.to_initval;
    	server->retrans_count = timeparms.to_retries;
    
    	/* Now create transport and client */
    	authflavour = RPC_AUTH_UNIX;
    	if (data->auth_flavourlen != 0) {
    		if (data->auth_flavourlen != 1) {
    			dprintk("%s: Invalid number of RPC auth flavours %d.\n",
    					__FUNCTION__, data->auth_flavourlen);
    			err = -EINVAL;
    			goto out_fail;
    		}
    		if (copy_from_user(&authflavour, data->auth_flavours, sizeof(authflavour))) {
    			err = -EFAULT;
    			goto out_fail;
    		}
    	}
    
    	server->client = nfs4_create_client(server, &timeparms, data->proto, authflavour);
    	if (IS_ERR(server->client)) {
    		err = PTR_ERR(server->client);
    			dprintk("%s: cannot create RPC client. Error = %d\n",
    					__FUNCTION__, err);
    			goto out_fail;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	sb->s_time_gran = 1;
    
    	sb->s_op = &nfs4_sops;
    	err = nfs_sb_init(sb, authflavour);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    static int nfs4_compare_super(struct super_block *sb, void *data)
    {
    	struct nfs_server *server = data;
    	struct nfs_server *old = NFS_SB(sb);
    
    	if (strcmp(server->hostname, old->hostname) != 0)
    		return 0;
    	if (strcmp(server->mnt_path, old->mnt_path) != 0)
    		return 0;
    	return 1;
    }
    
    static void *
    nfs_copy_user_string(char *dst, struct nfs_string *src, int maxlen)
    {
    	void *p = NULL;
    
    	if (!src->len)
    		return ERR_PTR(-EINVAL);
    	if (src->len < maxlen)
    		maxlen = src->len;
    	if (dst == NULL) {
    		p = dst = kmalloc(maxlen + 1, GFP_KERNEL);
    		if (p == NULL)
    			return ERR_PTR(-ENOMEM);
    	}
    	if (copy_from_user(dst, src->data, maxlen)) {
    
    Jesper Juhl's avatar
    Jesper Juhl committed
    		kfree(p);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return ERR_PTR(-EFAULT);
    	}
    	dst[maxlen] = '\0';
    	return dst;
    }
    
    static struct super_block *nfs4_get_sb(struct file_system_type *fs_type,
    	int flags, const char *dev_name, void *raw_data)
    {
    	int error;
    	struct nfs_server *server;
    	struct super_block *s;
    	struct nfs4_mount_data *data = raw_data;
    	void *p;
    
    
    	if (data == NULL) {
    		dprintk("%s: missing data argument\n", __FUNCTION__);
    		return ERR_PTR(-EINVAL);
    	}
    	if (data->version <= 0 || data->version > NFS4_MOUNT_VERSION) {
    		dprintk("%s: bad mount version\n", __FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return ERR_PTR(-EINVAL);
    	}
    
    
    	server = kzalloc(sizeof(struct nfs_server), GFP_KERNEL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!server)
    		return ERR_PTR(-ENOMEM);
    	/* Zero out the NFS state stuff */
    	init_nfsv4_state(server);
    
    	server->client = server->client_sys = server->client_acl = ERR_PTR(-EINVAL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	p = nfs_copy_user_string(NULL, &data->hostname, 256);
    	if (IS_ERR(p))
    		goto out_err;
    	server->hostname = p;
    
    	p = nfs_copy_user_string(NULL, &data->mnt_path, 1024);
    	if (IS_ERR(p))
    		goto out_err;
    	server->mnt_path = p;
    
    	p = nfs_copy_user_string(server->ip_addr, &data->client_addr,
    			sizeof(server->ip_addr) - 1);
    	if (IS_ERR(p))
    		goto out_err;
    
    	/* We now require that the mount process passes the remote address */
    	if (data->host_addrlen != sizeof(server->addr)) {
    		s = ERR_PTR(-EINVAL);
    		goto out_free;
    	}
    	if (copy_from_user(&server->addr, data->host_addr, sizeof(server->addr))) {
    		s = ERR_PTR(-EFAULT);
    		goto out_free;
    	}
    	if (server->addr.sin_family != AF_INET ||
    	    server->addr.sin_addr.s_addr == INADDR_ANY) {
    
    		dprintk("%s: mount program didn't pass remote IP address!\n",
    				__FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		s = ERR_PTR(-EINVAL);
    		goto out_free;
    	}
    
    
    	/* Fire up rpciod if not yet running */
    	s = ERR_PTR(rpciod_up());
    	if (IS_ERR(s)) {
    		dprintk("%s: couldn't start rpciod! Error = %ld\n",
    				__FUNCTION__, PTR_ERR(s));
    		goto out_free;
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	s = sget(fs_type, nfs4_compare_super, nfs_set_super, server);
    
    	if (IS_ERR(s) || s->s_root)
    		goto out_free;
    
    	s->s_flags = flags;
    
    
    	error = nfs4_fill_super(s, data, flags & MS_SILENT ? 1 : 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (error) {
    		up_write(&s->s_umount);
    		deactivate_super(s);
    		return ERR_PTR(error);
    	}
    	s->s_flags |= MS_ACTIVE;
    	return s;
    out_err:
    	s = (struct super_block *)p;
    out_free:
    
    Jesper Juhl's avatar
    Jesper Juhl committed
    	kfree(server->mnt_path);
    	kfree(server->hostname);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	kfree(server);
    	return s;
    }
    
    static void nfs4_kill_super(struct super_block *sb)
    {
    	struct nfs_server *server = NFS_SB(sb);
    
    	nfs_return_all_delegations(sb);
    	kill_anon_super(sb);
    
    	nfs4_renewd_prepare_shutdown(server);
    
    	if (server->client != NULL && !IS_ERR(server->client))
    		rpc_shutdown_client(server->client);
    
    	destroy_nfsv4_state(server);
    
    
    	nfs_free_iostats(server->io_stats);
    
    Jesper Juhl's avatar
    Jesper Juhl committed
    	kfree(server->hostname);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	kfree(server);
    
    	nfs_release_automount_timer();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static struct file_system_type nfs4_fs_type = {
    	.owner		= THIS_MODULE,
    	.name		= "nfs4",
    	.get_sb		= nfs4_get_sb,
    	.kill_sb	= nfs4_kill_super,
    	.fs_flags	= FS_ODD_RENAME|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
    };
    
    
    static const int nfs_set_port_min = 0;
    static const int nfs_set_port_max = 65535;
    static int param_set_port(const char *val, struct kernel_param *kp)
    {
    	char *endp;
    	int num = simple_strtol(val, &endp, 0);
    	if (endp == val || *endp || num < nfs_set_port_min || num > nfs_set_port_max)
    		return -EINVAL;
    	*((int *)kp->arg) = num;
    	return 0;
    }
    
    module_param_call(callback_tcpport, param_set_port, param_get_int,
    		 &nfs_callback_set_tcpport, 0644);
    
    
    static int param_set_idmap_timeout(const char *val, struct kernel_param *kp)
    {
    	char *endp;
    	int num = simple_strtol(val, &endp, 0);
    	int jif = num * HZ;
    	if (endp == val || *endp || num < 0 || jif < num)
    		return -EINVAL;
    	*((int *)kp->arg) = jif;
    	return 0;
    }
    
    module_param_call(idmap_cache_timeout, param_set_idmap_timeout, param_get_int,
    		 &nfs_idmap_cache_timeout, 0644);
    
    
    /* Constructs the SERVER-side path */
    static inline char *nfs4_path(const struct dentry *dentry, char *buffer, ssize_t buflen)
    {
    	return nfs_path(NFS_SB(dentry->d_sb)->mnt_path, dentry, buffer, buflen);
    }
    
    static inline char *nfs4_dup_path(const struct dentry *dentry)
    {
    	char *page = (char *) __get_free_page(GFP_USER);
    	char *path;
    
    	path = nfs4_path(dentry, page, PAGE_SIZE);
    	if (!IS_ERR(path)) {
    		int len = PAGE_SIZE + page - path;
    		char *tmp = path;
    
    		path = kmalloc(len, GFP_KERNEL);
    		if (path)
    			memcpy(path, tmp, len);
    		else
    			path = ERR_PTR(-ENOMEM);
    	}
    	free_page((unsigned long)page);
    	return path;
    }
    
    
    static struct super_block *nfs4_clone_sb(struct nfs_server *server, struct nfs_clone_mount *data)
    
    {
    	const struct dentry *dentry = data->dentry;
    	struct nfs4_client *clp = server->nfs4_state;
    	struct super_block *sb;
    
    
    	server->fsid = data->fattr->fsid;
    	nfs_copy_fh(&server->fh, data->fh);
    
    	server->mnt_path = nfs4_dup_path(dentry);
    	if (IS_ERR(server->mnt_path)) {
    		sb = (struct super_block *)server->mnt_path;
    		goto err;
    	}
    	sb = sget(&nfs4_fs_type, nfs4_compare_super, nfs_set_super, server);
    	if (IS_ERR(sb) || sb->s_root)
    		goto free_path;
    	nfs4_server_capabilities(server, &server->fh);
    
    	down_write(&clp->cl_sem);
    	atomic_inc(&clp->cl_count);
    	list_add_tail(&server->nfs4_siblings, &clp->cl_superblocks);
    	up_write(&clp->cl_sem);
    	return sb;
    free_path:
    	kfree(server->mnt_path);
    err:
    	server->mnt_path = NULL;
    	return sb;
    }
    
    static struct super_block *nfs_clone_nfs4_sb(struct file_system_type *fs_type,
    		int flags, const char *dev_name, void *raw_data)
    {
    	struct nfs_clone_mount *data = raw_data;
    
    	return nfs_clone_generic_sb(data, nfs4_clone_sb, nfs_clone_server);
    
    }
    
    static struct file_system_type clone_nfs4_fs_type = {
    	.owner		= THIS_MODULE,
    
    	.get_sb		= nfs_clone_nfs4_sb,
    	.kill_sb	= nfs4_kill_super,
    	.fs_flags	= FS_ODD_RENAME|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #define nfs4_init_once(nfsi) \
    	do { \
    		INIT_LIST_HEAD(&(nfsi)->open_states); \
    		nfsi->delegation = NULL; \
    		nfsi->delegation_state = 0; \
    		init_rwsem(&nfsi->rwsem); \
    	} while(0)
    
    
    static inline int register_nfs4fs(void)
    {
    	int ret;
    
    	ret = nfs_register_sysctl();
    	if (ret != 0)
    		return ret;
    	ret = register_filesystem(&nfs4_fs_type);
    	if (ret != 0)
    		nfs_unregister_sysctl();
    	return ret;
    }
    
    static inline void unregister_nfs4fs(void)
    {
    	unregister_filesystem(&nfs4_fs_type);
    	nfs_unregister_sysctl();
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #else
    
    #define nfs4_fill_sb(a,b)	ERR_PTR(-EINVAL)
    #define nfs4_fill_super(a,b)	ERR_PTR(-EINVAL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #define nfs4_init_once(nfsi) \
    	do { } while (0)
    #define register_nfs4fs() (0)
    #define unregister_nfs4fs()
    #endif
    
    
    static inline char *nfs_devname(const struct vfsmount *mnt_parent,
    			 const struct dentry *dentry,
    			 char *buffer, ssize_t buflen)
    {
    	return nfs_path(mnt_parent->mnt_devname, dentry, buffer, buflen);
    }
    
    /**
     * nfs_do_submount - set up mountpoint when crossing a filesystem boundary
     * @mnt_parent - mountpoint of parent directory
     * @dentry - parent directory
     * @fh - filehandle for new root dentry
     * @fattr - attributes for new root inode
     *
     */
    struct vfsmount *nfs_do_submount(const struct vfsmount *mnt_parent,
    		const struct dentry *dentry, struct nfs_fh *fh,
    		struct nfs_fattr *fattr)
    {
    	struct nfs_clone_mount mountdata = {
    		.sb = mnt_parent->mnt_sb,
    		.dentry = dentry,
    		.fh = fh,
    		.fattr = fattr,
    	};
    	struct vfsmount *mnt = ERR_PTR(-ENOMEM);
    	char *page = (char *) __get_free_page(GFP_USER);
    	char *devname;
    
    	dprintk("%s: submounting on %s/%s\n", __FUNCTION__,
    			dentry->d_parent->d_name.name,
    			dentry->d_name.name);
    	if (page == NULL)
    		goto out;
    	devname = nfs_devname(mnt_parent, dentry, page, PAGE_SIZE);
    	mnt = (struct vfsmount *)devname;
    	if (IS_ERR(devname))
    		goto free_page;
    	switch (NFS_SB(mnt_parent->mnt_sb)->rpc_ops->version) {
    		case 2:
    		case 3:
    			mnt = vfs_kern_mount(&clone_nfs_fs_type, 0, devname, &mountdata);
    			break;
    		case 4:
    			mnt = vfs_kern_mount(&clone_nfs4_fs_type, 0, devname, &mountdata);
    			break;
    		default:
    			BUG();
    	}
    free_page:
    	free_page((unsigned long)page);
    out:
    	dprintk("%s: done\n", __FUNCTION__);
    	return mnt;
    }
    
    
    /* Check if fs_root is valid */
    static inline char *nfs4_pathname_string(struct nfs4_pathname *pathname, char *buffer, ssize_t buflen)
    {
    	char *end = buffer + buflen;
    	int n;
    
    	*--end = '\0';
    	buflen--;
    
    	n = pathname->ncomponents;
    	while (--n >= 0) {
    		struct nfs4_string *component = &pathname->components[n];
    		buflen -= component->len + 1;
    		if (buflen < 0)
    			goto Elong;
    		end -= component->len;
    		memcpy(end, component->data, component->len);
    		*--end = '/';
    	}
    	return end;
    Elong:
    	return ERR_PTR(-ENAMETOOLONG);
    }
    
    /* Check if the string represents a "valid" IPv4 address */
    static inline int valid_ipaddr4(const char *buf)
    {
    	int rc, count, in[4];
    
    	rc = sscanf(buf, "%d.%d.%d.%d", &in[0], &in[1], &in[2], &in[3]);
    	if (rc != 4)
    		return -EINVAL;
    	for (count = 0; count < 4; count++) {
    		if (in[count] > 255)
    			return -EINVAL;
    	}
    	return 0;
    }
    
    static struct super_block *nfs4_referral_sb(struct nfs_server *server, struct nfs_clone_mount *data)
    {
    	struct super_block *sb = ERR_PTR(-ENOMEM);
    	int len;
    
    	len = strlen(data->mnt_path) + 1;
    	server->mnt_path = kmalloc(len, GFP_KERNEL);
    	if (server->mnt_path == NULL)
    		goto err;
    	memcpy(server->mnt_path, data->mnt_path, len);
    	memcpy(&server->addr, data->addr, sizeof(struct sockaddr_in));
    
    	sb = sget(&nfs4_fs_type, nfs4_compare_super, nfs_set_super, server);
    	if (IS_ERR(sb) || sb->s_root)
    		goto free_path;
    	return sb;
    free_path:
    	kfree(server->mnt_path);
    err:
    	server->mnt_path = NULL;
    	return sb;
    }
    
    static struct nfs_server *nfs4_referral_server(struct super_block *sb, struct nfs_clone_mount *data)
    {
    	struct nfs_server *server = NFS_SB(sb);
    	struct rpc_timeout timeparms;
    	int proto, timeo, retrans;
    	void *err;
    
    	proto = IPPROTO_TCP;
    	/* Since we are following a referral and there may be alternatives,
    	   set the timeouts and retries to low values */
    	timeo = 2;
    	retrans = 1;
    	nfs_init_timeout_values(&timeparms, proto, timeo, retrans);
    
    	server->client = nfs4_create_client(server, &timeparms, proto, data->authflavor);
    	if (IS_ERR((err = server->client)))
    		goto out_err;
    
    	sb->s_time_gran = 1;
    	sb->s_op = &nfs4_sops;
    	err = ERR_PTR(nfs_sb_init(sb, data->authflavor));
    	if (!IS_ERR(err))
    		return server;
    out_err:
    	return (struct nfs_server *)err;
    }
    
    static struct super_block *nfs_referral_nfs4_sb(struct file_system_type *fs_type,
    		int flags, const char *dev_name, void *raw_data)
    {
    	struct nfs_clone_mount *data = raw_data;
    	return nfs_clone_generic_sb(data, nfs4_referral_sb, nfs4_referral_server);
    }
    
    static struct file_system_type nfs_referral_nfs4_fs_type = {
    	.owner		= THIS_MODULE,
    	.name		= "nfs4",
    	.get_sb		= nfs_referral_nfs4_sb,
    	.kill_sb	= nfs4_kill_super,
    	.fs_flags	= FS_ODD_RENAME|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
    };
    
    /**
     * nfs_follow_referral - set up mountpoint when hitting a referral on moved error
     * @mnt_parent - mountpoint of parent directory
     * @dentry - parent directory
     * @fspath - fs path returned in fs_locations
     * @mntpath - mount path to new server
     * @hostname - hostname of new server
     * @addr - host addr of new server
     *
     */
    struct vfsmount *nfs_follow_referral(const struct vfsmount *mnt_parent,
    	     const struct dentry *dentry, struct nfs4_fs_locations *locations)
    {
    	struct vfsmount *mnt = ERR_PTR(-ENOENT);
    	struct nfs_clone_mount mountdata = {
    		.sb = mnt_parent->mnt_sb,
    		.dentry = dentry,
    		.authflavor = NFS_SB(mnt_parent->mnt_sb)->client->cl_auth->au_flavor,
    	};
    	char *page, *page2;
    	char *path, *fs_path;
    	char *devname;
    	int loc, s;
    
    	if (locations == NULL || locations->nlocations <= 0)
    		goto out;
    
    	dprintk("%s: referral at %s/%s\n", __FUNCTION__,
    		dentry->d_parent->d_name.name, dentry->d_name.name);
    
    	/* Ensure fs path is a prefix of current dentry path */
    	page = (char *) __get_free_page(GFP_USER);
    	if (page == NULL)
    		goto out;
    	page2 = (char *) __get_free_page(GFP_USER);
    	if (page2 == NULL)
    		goto out;
    
    	path = nfs4_path(dentry, page, PAGE_SIZE);
    	if (IS_ERR(path))
    		goto out_free;
    
    	fs_path = nfs4_pathname_string(&locations->fs_path, page2, PAGE_SIZE);
    	if (IS_ERR(fs_path))
    		goto out_free;
    
    	if (strncmp(path, fs_path, strlen(fs_path)) != 0) {
    		dprintk("%s: path %s does not begin with fsroot %s\n", __FUNCTION__, path, fs_path);
    		goto out_free;
    	}
    
    	devname = nfs_devname(mnt_parent, dentry, page, PAGE_SIZE);
    	if (IS_ERR(devname)) {
    		mnt = (struct vfsmount *)devname;
    		goto out_free;
    	}
    
    	loc = 0;
    	while (loc < locations->nlocations && IS_ERR(mnt)) {
    		struct nfs4_fs_location *location = &locations->locations[loc];
    		char *mnt_path;
    
    		if (location == NULL || location->nservers <= 0 ||
    		    location->rootpath.ncomponents == 0) {
    			loc++;
    			continue;
    		}
    
    		mnt_path = nfs4_pathname_string(&location->rootpath, page2, PAGE_SIZE);
    		if (IS_ERR(mnt_path)) {
    			loc++;
    			continue;
    		}
    		mountdata.mnt_path = mnt_path;
    
    		s = 0;
    		while (s < location->nservers) {
    			struct sockaddr_in addr = {};
    
    			if (location->servers[s].len <= 0 ||
    			    valid_ipaddr4(location->servers[s].data) < 0) {
    				s++;
    				continue;
    			}
    
    			mountdata.hostname = location->servers[s].data;
    			addr.sin_addr.s_addr = in_aton(mountdata.hostname);
    			addr.sin_family = AF_INET;
    			addr.sin_port = htons(NFS_PORT);
    			mountdata.addr = &addr;
    
    			mnt = vfs_kern_mount(&nfs_referral_nfs4_fs_type, 0, devname, &mountdata);
    			if (!IS_ERR(mnt)) {
    				break;
    			}
    			s++;
    		}
    		loc++;
    	}
    
    out_free:
    	free_page((unsigned long)page);
    	free_page((unsigned long)page2);
    out:
    	dprintk("%s: done\n", __FUNCTION__);
    	return mnt;
    }
    
    /*
     * nfs_do_refmount - handle crossing a referral on server
     * @dentry - dentry of referral
     * @nd - nameidata info
     *
     */
    struct vfsmount *nfs_do_refmount(const struct vfsmount *mnt_parent, struct dentry *dentry)
    {
    	struct vfsmount *mnt = ERR_PTR(-ENOENT);
    	struct dentry *parent;
    	struct nfs4_fs_locations *fs_locations = NULL;
    	struct page *page;
    	int err;
    
    	/* BUG_ON(IS_ROOT(dentry)); */
    	dprintk("%s: enter\n", __FUNCTION__);
    
    	page = alloc_page(GFP_KERNEL);
    	if (page == NULL)
    		goto out;
    
    	fs_locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL);
    	if (fs_locations == NULL)
    		goto out_free;
    
    	/* Get locations */
    	parent = dget_parent(dentry);
    	dprintk("%s: getting locations for %s/%s\n", __FUNCTION__, parent->d_name.name, dentry->d_name.name);
    	err = nfs4_proc_fs_locations(parent->d_inode, dentry, fs_locations, page);
    	dput(parent);
    	if (err != 0 || fs_locations->nlocations <= 0 ||
    	    fs_locations->fs_path.ncomponents <= 0)
    		goto out_free;
    
    	mnt = nfs_follow_referral(mnt_parent, dentry, fs_locations);
    out_free:
    	__free_page(page);
    	kfree(fs_locations);
    out:
    	dprintk("%s: done\n", __FUNCTION__);
    	return mnt;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    extern int nfs_init_nfspagecache(void);
    extern void nfs_destroy_nfspagecache(void);
    extern int nfs_init_readpagecache(void);
    extern void nfs_destroy_readpagecache(void);
    extern int nfs_init_writepagecache(void);
    extern void nfs_destroy_writepagecache(void);
    #ifdef CONFIG_NFS_DIRECTIO
    extern int nfs_init_directcache(void);
    extern void nfs_destroy_directcache(void);
    #endif
    
    static kmem_cache_t * nfs_inode_cachep;
    
    static struct inode *nfs_alloc_inode(struct super_block *sb)
    {
    	struct nfs_inode *nfsi;
    	nfsi = (struct nfs_inode *)kmem_cache_alloc(nfs_inode_cachep, SLAB_KERNEL);
    	if (!nfsi)
    		return NULL;
    
    	nfsi->flags = 0UL;
    	nfsi->cache_validity = 0UL;
    
    	nfsi->cache_change_attribute = jiffies;
    
    #ifdef CONFIG_NFS_V3_ACL
    	nfsi->acl_access = ERR_PTR(-EAGAIN);
    	nfsi->acl_default = ERR_PTR(-EAGAIN);
    #endif
    
    #ifdef CONFIG_NFS_V4
    	nfsi->nfs4_acl = NULL;
    #endif /* CONFIG_NFS_V4 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return &nfsi->vfs_inode;
    }
    
    static void nfs_destroy_inode(struct inode *inode)
    {
    	kmem_cache_free(nfs_inode_cachep, NFS_I(inode));
    }
    
    static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
    {
    	struct nfs_inode *nfsi = (struct nfs_inode *) foo;
    
    	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
    	    SLAB_CTOR_CONSTRUCTOR) {
    		inode_init_once(&nfsi->vfs_inode);
    		spin_lock_init(&nfsi->req_lock);
    		INIT_LIST_HEAD(&nfsi->dirty);
    		INIT_LIST_HEAD(&nfsi->commit);
    		INIT_LIST_HEAD(&nfsi->open_files);
    		INIT_RADIX_TREE(&nfsi->nfs_page_tree, GFP_ATOMIC);
    		atomic_set(&nfsi->data_updates, 0);
    		nfsi->ndirty = 0;
    		nfsi->ncommit = 0;
    		nfsi->npages = 0;
    		nfs4_init_once(nfsi);
    	}
    }
     
    
    static int nfs_init_inodecache(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	nfs_inode_cachep = kmem_cache_create("nfs_inode_cache",
    					     sizeof(struct nfs_inode),
    
    					     0, (SLAB_RECLAIM_ACCOUNT|
    						SLAB_MEM_SPREAD),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					     init_once, NULL);
    	if (nfs_inode_cachep == NULL)
    		return -ENOMEM;
    
    	return 0;
    }
    
    
    static void nfs_destroy_inodecache(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (kmem_cache_destroy(nfs_inode_cachep))
    		printk(KERN_INFO "nfs_inode_cache: not all structures were freed\n");
    }
    
    /*
     * Initialize NFS
     */
    static int __init init_nfs_fs(void)
    {
    	int err;
    
    	err = nfs_init_nfspagecache();
    	if (err)
    		goto out4;
    
    	err = nfs_init_inodecache();
    	if (err)
    		goto out3;
    
    	err = nfs_init_readpagecache();
    	if (err)
    		goto out2;
    
    	err = nfs_init_writepagecache();
    	if (err)
    		goto out1;
    
    #ifdef CONFIG_NFS_DIRECTIO
    	err = nfs_init_directcache();
    	if (err)
    		goto out0;
    #endif
    
    #ifdef CONFIG_PROC_FS
    	rpc_proc_register(&nfs_rpcstat);
    #endif
            err = register_filesystem(&nfs_fs_type);
    	if (err)
    		goto out;
    	if ((err = register_nfs4fs()) != 0)
    		goto out;
    	return 0;
    out:
    #ifdef CONFIG_PROC_FS
    	rpc_proc_unregister("nfs");
    #endif
    #ifdef CONFIG_NFS_DIRECTIO
    	nfs_destroy_directcache();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    	nfs_destroy_writepagecache();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out1:
    	nfs_destroy_readpagecache();
    out2:
    	nfs_destroy_inodecache();
    out3:
    	nfs_destroy_nfspagecache();
    out4:
    	return err;
    }
    
    static void __exit exit_nfs_fs(void)
    {
    #ifdef CONFIG_NFS_DIRECTIO
    	nfs_destroy_directcache();
    #endif
    	nfs_destroy_writepagecache();
    	nfs_destroy_readpagecache();
    	nfs_destroy_inodecache();
    	nfs_destroy_nfspagecache();
    #ifdef CONFIG_PROC_FS
    	rpc_proc_unregister("nfs");
    #endif
    	unregister_filesystem(&nfs_fs_type);
    	unregister_nfs4fs();
    }
    
    /* Not quite true; I just maintain it */
    MODULE_AUTHOR("Olaf Kirch <okir@monad.swb.de>");
    MODULE_LICENSE("GPL");
    
    module_init(init_nfs_fs)
    module_exit(exit_nfs_fs)