Skip to content
Snippets Groups Projects
nfs4proc.c 161 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	.rpc_release = nfs4_free_closedata,
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* 
     * It is possible for data to be read/written from a mem-mapped file 
     * after the sys_close call (which hits the vfs layer as a flush).
     * This means that we can't safely call nfsv4 close on a file until 
     * the inode is cleared. This in turn means that we are not good
     * NFSv4 citizens - we do not indicate to the server to update the file's 
     * share state even when we are done with one of the three share 
     * stateid's in the inode.
     *
     * NOTE: Caller must be holding the sp->so_owner semaphore!
     */
    
    Fred Isaman's avatar
    Fred Isaman committed
    int nfs4_do_close(struct path *path, struct nfs4_state *state, gfp_t gfp_mask, int wait, bool roc)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_server *server = NFS_SERVER(state->inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_closedata *calldata;
    
    	struct nfs4_state_owner *sp = state->owner;
    	struct rpc_task *task;
    
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE],
    		.rpc_cred = state->owner->so_cred,
    	};
    
    	struct rpc_task_setup task_setup_data = {
    		.rpc_client = server->client,
    
    		.callback_ops = &nfs4_close_ops,
    
    		.flags = RPC_TASK_ASYNC,
    	};
    
    	int status = -ENOMEM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	calldata = kzalloc(sizeof(*calldata), gfp_mask);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (calldata == NULL)
    
    	calldata->inode = state->inode;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	calldata->state = state;
    
    	calldata->arg.fh = NFS_FH(state->inode);
    
    	calldata->arg.stateid = &state->open_stateid;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Serialization for the sequence id */
    
    	calldata->arg.seqid = nfs_alloc_seqid(&state->owner->so_seqid, gfp_mask);
    
    	if (calldata->arg.seqid == NULL)
    		goto out_free_calldata;
    
    	calldata->arg.fmode = 0;
    
    	calldata->arg.bitmask = server->cache_consistency_bitmask;
    
    	calldata->res.fattr = &calldata->fattr;
    
    	calldata->res.seqid = calldata->arg.seqid;
    
    Fred Isaman's avatar
    Fred Isaman committed
    	calldata->roc = roc;
    
    	path_get(path);
    	calldata->path = *path;
    
    	msg.rpc_argp = &calldata->arg;
    	msg.rpc_resp = &calldata->res;
    
    	task_setup_data.callback_data = calldata;
    	task = rpc_run_task(&task_setup_data);
    
    	if (IS_ERR(task))
    		return PTR_ERR(task);
    
    	status = 0;
    	if (wait)
    		status = rpc_wait_for_completion_task(task);
    
    	rpc_put_task(task);
    
    out_free_calldata:
    	kfree(calldata);
    out:
    
    Fred Isaman's avatar
    Fred Isaman committed
    	if (roc)
    		pnfs_roc_release(state->inode);
    
    	nfs4_put_open_state(state);
    	nfs4_put_state_owner(sp);
    
    	return status;
    
    nfs4_atomic_open(struct inode *dir, struct nfs_open_context *ctx, int open_flags, struct iattr *attr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs4_state *state;
    
    
    	/* Protect against concurrent sillydeletes */
    
    	state = nfs4_do_open(dir, &ctx->path, ctx->mode, open_flags, attr, ctx->cred);
    
    	if (IS_ERR(state))
    		return ERR_CAST(state);
    
    	ctx->state = state;
    
    	return igrab(state->inode);
    
    static void nfs4_close_context(struct nfs_open_context *ctx, int is_sync)
    
    {
    	if (ctx->state == NULL)
    		return;
    	if (is_sync)
    		nfs4_close_sync(&ctx->path, ctx->state, ctx->mode);
    	else
    		nfs4_close_state(&ctx->path, ctx->state, ctx->mode);
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fhandle)
    {
    
    	struct nfs4_server_caps_arg args = {
    		.fhandle = fhandle,
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_server_caps_res res = {};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SERVER_CAPS],
    
    		.rpc_argp = &args,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.rpc_resp = &res,
    	};
    	int status;
    
    
    	status = nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status == 0) {
    		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
    
    		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
    				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
    				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
    				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
    				NFS_CAP_CTIME|NFS_CAP_MTIME);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL)
    			server->caps |= NFS_CAP_ACLS;
    		if (res.has_links != 0)
    			server->caps |= NFS_CAP_HARDLINKS;
    		if (res.has_symlinks != 0)
    			server->caps |= NFS_CAP_SYMLINKS;
    
    		if (res.attr_bitmask[0] & FATTR4_WORD0_FILEID)
    			server->caps |= NFS_CAP_FILEID;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_MODE)
    			server->caps |= NFS_CAP_MODE;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_NUMLINKS)
    			server->caps |= NFS_CAP_NLINK;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_OWNER)
    			server->caps |= NFS_CAP_OWNER;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_OWNER_GROUP)
    			server->caps |= NFS_CAP_OWNER_GROUP;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_TIME_ACCESS)
    			server->caps |= NFS_CAP_ATIME;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_TIME_METADATA)
    			server->caps |= NFS_CAP_CTIME;
    		if (res.attr_bitmask[1] & FATTR4_WORD1_TIME_MODIFY)
    			server->caps |= NFS_CAP_MTIME;
    
    
    		memcpy(server->cache_consistency_bitmask, res.attr_bitmask, sizeof(server->cache_consistency_bitmask));
    		server->cache_consistency_bitmask[0] &= FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE;
    		server->cache_consistency_bitmask[1] &= FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		server->acl_bitmask = res.acl_bitmask;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    int nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fhandle)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(server,
    				_nfs4_server_capabilities(server, fhandle),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_lookup_root(struct nfs_server *server, struct nfs_fh *fhandle,
    		struct nfs_fsinfo *info)
    {
    	struct nfs4_lookup_root_arg args = {
    		.bitmask = nfs4_fattr_bitmap,
    	};
    	struct nfs4_lookup_res res = {
    		.server = server,
    
    		.fattr = info->fattr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.fh = fhandle,
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LOOKUP_ROOT],
    		.rpc_argp = &args,
    		.rpc_resp = &res,
    	};
    
    	nfs_fattr_init(info->fattr);
    
    	return nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static int nfs4_lookup_root(struct nfs_server *server, struct nfs_fh *fhandle,
    		struct nfs_fsinfo *info)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    
    		err = _nfs4_lookup_root(server, fhandle, info);
    		switch (err) {
    		case 0:
    		case -NFS4ERR_WRONGSEC:
    			break;
    		default:
    			err = nfs4_handle_exception(server, err, &exception);
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	} while (exception.retry);
    	return err;
    }
    
    
    static int nfs4_lookup_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
    				struct nfs_fsinfo *info, rpc_authflavor_t flavor)
    {
    	struct rpc_auth *auth;
    	int ret;
    
    	auth = rpcauth_create(flavor, server->client);
    	if (!auth) {
    		ret = -EIO;
    		goto out;
    	}
    	ret = nfs4_lookup_root(server, fhandle, info);
    out:
    	return ret;
    }
    
    
    static int nfs4_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	int i, len, status = 0;
    
    	rpc_authflavor_t flav_array[NFS_MAX_SECFLAVORS];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	len = gss_mech_list_pseudoflavors(&flav_array[0]);
    	flav_array[len] = RPC_AUTH_NULL;
    	len += 1;
    
    
    	for (i = 0; i < len; i++) {
    		status = nfs4_lookup_root_sec(server, fhandle, info, flav_array[i]);
    
    		if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
    
    	/*
    	 * -EACCESS could mean that the user doesn't have correct permissions
    	 * to access the mount.  It could also mean that we tried to mount
    	 * with a gss auth flavor, but rpc.gssd isn't running.  Either way,
    	 * existing mount programs don't handle -EACCES very well so it should
    	 * be mapped to -EPERM instead.
    	 */
    	if (status == -EACCES)
    		status = -EPERM;
    
    	return status;
    }
    
    /*
     * get the file handle for the "/" directory on the server
     */
    static int nfs4_proc_get_root(struct nfs_server *server, struct nfs_fh *fhandle,
    			      struct nfs_fsinfo *info)
    {
    	int status = nfs4_lookup_root(server, fhandle, info);
    
    	if ((status == -NFS4ERR_WRONGSEC) && !(server->flags & NFS_MOUNT_SECFLAVOUR))
    		/*
    		 * A status of -NFS4ERR_WRONGSEC will be mapped to -EPERM
    		 * by nfs4_map_errors() as this function exits.
    		 */
    
    		status = nfs4_find_root_sec(server, fhandle, info);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status == 0)
    		status = nfs4_server_capabilities(server, fhandle);
    	if (status == 0)
    		status = nfs4_do_fsinfo(server, fhandle, info);
    
    Manoj Naik's avatar
    Manoj Naik committed
    /*
     * Get locations and (maybe) other attributes of a referral.
     * Note that we'll actually follow the referral later when
     * we detect fsid mismatch in inode revalidation
     */
    
    static int nfs4_get_referral(struct inode *dir, const struct qstr *name, struct nfs_fattr *fattr, struct nfs_fh *fhandle)
    
    Manoj Naik's avatar
    Manoj Naik committed
    {
    	int status = -ENOMEM;
    	struct page *page = NULL;
    	struct nfs4_fs_locations *locations = NULL;
    
    	page = alloc_page(GFP_KERNEL);
    	if (page == NULL)
    		goto out;
    	locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL);
    	if (locations == NULL)
    		goto out;
    
    
    	status = nfs4_proc_fs_locations(dir, name, locations, page);
    
    Manoj Naik's avatar
    Manoj Naik committed
    	if (status != 0)
    		goto out;
    	/* Make sure server returned a different fsid for the referral */
    	if (nfs_fsid_equal(&NFS_SERVER(dir)->fsid, &locations->fattr.fsid)) {
    
    		dprintk("%s: server did not return a different fsid for a referral at %s\n", __func__, name->name);
    
    Manoj Naik's avatar
    Manoj Naik committed
    		status = -EIO;
    		goto out;
    	}
    
    	memcpy(fattr, &locations->fattr, sizeof(struct nfs_fattr));
    	fattr->valid |= NFS_ATTR_FATTR_V4_REFERRAL;
    	if (!fattr->mode)
    		fattr->mode = S_IFDIR;
    	memset(fhandle, 0, sizeof(struct nfs_fh));
    out:
    	if (page)
    		__free_page(page);
    
    Manoj Naik's avatar
    Manoj Naik committed
    	return status;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int _nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr)
    {
    	struct nfs4_getattr_arg args = {
    		.fh = fhandle,
    		.bitmask = server->attr_bitmask,
    	};
    	struct nfs4_getattr_res res = {
    		.fattr = fattr,
    		.server = server,
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETATTR],
    		.rpc_argp = &args,
    		.rpc_resp = &res,
    	};
    	
    
    	nfs_fattr_init(fattr);
    
    	return nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static int nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(server,
    				_nfs4_proc_getattr(server, fhandle, fattr),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    /* 
     * The file is not closed if it is opened due to the a request to change
     * the size of the file. The open call will not be needed once the
     * VFS layer lookup-intents are implemented.
     *
     * Close is called when the inode is destroyed.
     * If we haven't opened the file for O_WRONLY, we
     * need to in the size_change case to obtain a stateid.
     *
     * Got race?
     * Because OPEN is always done by name in nfsv4, it is
     * possible that we opened a different file by the same
     * name.  We can recognize this race condition, but we
     * can't do anything about it besides returning an error.
     *
     * This will be fixed with VFS changes (lookup-intent).
     */
    static int
    nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr,
    		  struct iattr *sattr)
    {
    
    	struct inode *inode = dentry->d_inode;
    
    	struct rpc_cred *cred = NULL;
    
    	struct nfs4_state *state = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int status;
    
    
    	nfs_fattr_init(fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	
    
    	/* Search for an existing open(O_WRITE) file */
    
    	if (sattr->ia_valid & ATTR_FILE) {
    		struct nfs_open_context *ctx;
    
    		ctx = nfs_file_open_context(sattr->ia_file);
    
    		if (ctx) {
    			cred = ctx->cred;
    			state = ctx->state;
    		}
    
    	status = nfs4_do_setattr(inode, cred, fattr, sattr, state);
    
    	if (status == 0)
    		nfs_setattr_update_inode(inode, sattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    static int _nfs4_proc_lookupfh(struct rpc_clnt *clnt, struct nfs_server *server,
    		const struct nfs_fh *dirfh, const struct qstr *name,
    		struct nfs_fh *fhandle, struct nfs_fattr *fattr)
    
    {
    	int		       status;
    	struct nfs4_lookup_arg args = {
    		.bitmask = server->attr_bitmask,
    		.dir_fh = dirfh,
    		.name = name,
    	};
    	struct nfs4_lookup_res res = {
    		.server = server,
    		.fattr = fattr,
    		.fh = fhandle,
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LOOKUP],
    		.rpc_argp = &args,
    		.rpc_resp = &res,
    	};
    
    	nfs_fattr_init(fattr);
    
    	dprintk("NFS call  lookupfh %s\n", name->name);
    
    	status = nfs4_call_sync(clnt, server, &msg, &args.seq_args, &res.seq_res, 0);
    
    	dprintk("NFS reply lookupfh: %d\n", status);
    	return status;
    }
    
    static int nfs4_proc_lookupfh(struct nfs_server *server, struct nfs_fh *dirfh,
    			      struct qstr *name, struct nfs_fh *fhandle,
    			      struct nfs_fattr *fattr)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    
    		err = _nfs4_proc_lookupfh(server->client, server, dirfh, name, fhandle, fattr);
    
    		/* FIXME: !!!! */
    		if (err == -NFS4ERR_MOVED) {
    			err = -EREMOTE;
    			break;
    		}
    		err = nfs4_handle_exception(server, err, &exception);
    
    	} while (exception.retry);
    	return err;
    }
    
    
    static int _nfs4_proc_lookup(struct rpc_clnt *clnt, struct inode *dir,
    		const struct qstr *name, struct nfs_fh *fhandle,
    		struct nfs_fattr *fattr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	
    	dprintk("NFS call  lookup %s\n", name->name);
    
    	status = _nfs4_proc_lookupfh(clnt, NFS_SERVER(dir), NFS_FH(dir), name, fhandle, fattr);
    
    Manoj Naik's avatar
    Manoj Naik committed
    	if (status == -NFS4ERR_MOVED)
    		status = nfs4_get_referral(dir, name, fattr, fhandle);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dprintk("NFS reply lookup: %d\n", status);
    	return status;
    }
    
    
    void nfs_fixup_secinfo_attributes(struct nfs_fattr *fattr, struct nfs_fh *fh)
    {
    	memset(fh, 0, sizeof(struct nfs_fh));
    	fattr->fsid.major = 1;
    	fattr->valid |= NFS_ATTR_FATTR_TYPE | NFS_ATTR_FATTR_MODE |
    		NFS_ATTR_FATTR_NLINK | NFS_ATTR_FATTR_FSID | NFS_ATTR_FATTR_MOUNTPOINT;
    	fattr->mode = S_IFDIR | S_IRUGO | S_IXUGO;
    	fattr->nlink = 2;
    }
    
    
    static int nfs4_proc_lookup(struct rpc_clnt *clnt, struct inode *dir, struct qstr *name,
    			    struct nfs_fh *fhandle, struct nfs_fattr *fattr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(dir),
    
    				_nfs4_proc_lookup(clnt, dir, name, fhandle, fattr),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				&exception);
    
    		if (err == -EPERM)
    			nfs_fixup_secinfo_attributes(fattr, fhandle);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry)
    {
    
    	struct nfs_server *server = NFS_SERVER(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_accessargs args = {
    		.fh = NFS_FH(inode),
    
    		.bitmask = server->attr_bitmask,
    	};
    	struct nfs4_accessres res = {
    		.server = server,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ACCESS],
    		.rpc_argp = &args,
    		.rpc_resp = &res,
    		.rpc_cred = entry->cred,
    	};
    	int mode = entry->mask;
    	int status;
    
    	/*
    	 * Determine which access bits we want to ask for...
    	 */
    	if (mode & MAY_READ)
    		args.access |= NFS4_ACCESS_READ;
    	if (S_ISDIR(inode->i_mode)) {
    		if (mode & MAY_WRITE)
    			args.access |= NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND | NFS4_ACCESS_DELETE;
    		if (mode & MAY_EXEC)
    			args.access |= NFS4_ACCESS_LOOKUP;
    	} else {
    		if (mode & MAY_WRITE)
    			args.access |= NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND;
    		if (mode & MAY_EXEC)
    			args.access |= NFS4_ACCESS_EXECUTE;
    	}
    
    
    	res.fattr = nfs_alloc_fattr();
    	if (res.fattr == NULL)
    		return -ENOMEM;
    
    
    	status = nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!status) {
    		entry->mask = 0;
    		if (res.access & NFS4_ACCESS_READ)
    			entry->mask |= MAY_READ;
    		if (res.access & (NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND | NFS4_ACCESS_DELETE))
    			entry->mask |= MAY_WRITE;
    		if (res.access & (NFS4_ACCESS_LOOKUP|NFS4_ACCESS_EXECUTE))
    			entry->mask |= MAY_EXEC;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    static int nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(inode),
    				_nfs4_proc_access(inode, entry),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    /*
     * TODO: For the time being, we don't try to get any attributes
     * along with any of the zero-copy operations READ, READDIR,
     * READLINK, WRITE.
     *
     * In the case of the first three, we want to put the GETATTR
     * after the read-type operation -- this is because it is hard
     * to predict the length of a GETATTR response in v4, and thus
     * align the READ data correctly.  This means that the GETATTR
     * may end up partially falling into the page cache, and we should
     * shift it into the 'tail' of the xdr_buf before processing.
     * To do this efficiently, we need to know the total length
     * of data received, which doesn't seem to be available outside
     * of the RPC layer.
     *
     * In the case of WRITE, we also want to put the GETATTR after
     * the operation -- in this case because we want to make sure
     * we get the post-operation mtime and size.  This means that
     * we can't use xdr_encode_pages() as written: we need a variant
     * of it which would leave room in the 'tail' iovec.
     *
     * Both of these changes to the XDR layer would in fact be quite
     * minor, but I decided to leave them for a subsequent patch.
     */
    static int _nfs4_proc_readlink(struct inode *inode, struct page *page,
    		unsigned int pgbase, unsigned int pglen)
    {
    	struct nfs4_readlink args = {
    		.fh       = NFS_FH(inode),
    		.pgbase	  = pgbase,
    		.pglen    = pglen,
    		.pages    = &page,
    	};
    
    	struct nfs4_readlink_res res;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READLINK],
    		.rpc_argp = &args,
    
    		.rpc_resp = &res,
    
    	return nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode), &msg, &args.seq_args, &res.seq_res, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static int nfs4_proc_readlink(struct inode *inode, struct page *page,
    		unsigned int pgbase, unsigned int pglen)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(inode),
    				_nfs4_proc_readlink(inode, page, pgbase, pglen),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    /*
     * Got race?
     * We will need to arrange for the VFS layer to provide an atomic open.
     * Until then, this create/open method is prone to inefficiency and race
     * conditions due to the lookup, create, and open VFS calls from sys_open()
     * placed on the wire.
     *
     * Given the above sorry state of affairs, I'm simply sending an OPEN.
     * The file will be opened again in the subsequent VFS open call
     * (nfs4_proc_file_open).
     *
     * The open for read will just hang around to be used by any process that
     * opens the file O_RDONLY. This will all be resolved with the VFS changes.
     */
    
    static int
    nfs4_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr,
    
                     int flags, struct nfs_open_context *ctx)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct path my_path = {
    
    	struct path *path = &my_path;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_state *state;
    
    	struct rpc_cred *cred = NULL;
    	fmode_t fmode = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int status = 0;
    
    
    	if (ctx != NULL) {
    		cred = ctx->cred;
    		path = &ctx->path;
    		fmode = ctx->mode;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	sattr->ia_mode &= ~current_umask();
    
    	state = nfs4_do_open(dir, path, fmode, flags, sattr, cred);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (IS_ERR(state)) {
    		status = PTR_ERR(state);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	d_add(dentry, igrab(state->inode));
    
    	nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
    
    	if (ctx != NULL)
    		ctx->state = state;
    
    		nfs4_close_sync(path, state, fmode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out:
    	return status;
    }
    
    static int _nfs4_proc_remove(struct inode *dir, struct qstr *name)
    {
    
    	struct nfs_server *server = NFS_SERVER(dir);
    
    	struct nfs_removeargs args = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.fh = NFS_FH(dir),
    
    		.name.len = name->len,
    		.name.name = name->name,
    
    		.bitmask = server->attr_bitmask,
    	};
    
    	struct nfs_removeres res = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct rpc_message msg = {
    
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_REMOVE],
    		.rpc_argp = &args,
    		.rpc_resp = &res,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	int status = -ENOMEM;
    
    	res.dir_attr = nfs_alloc_fattr();
    	if (res.dir_attr == NULL)
    		goto out;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	status = nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 1);
    
    	if (status == 0) {
    		update_changeattr(dir, &res.cinfo);
    
    		nfs_post_op_update_inode(dir, res.dir_attr);
    
    	nfs_free_fattr(res.dir_attr);
    out:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    static int nfs4_proc_remove(struct inode *dir, struct qstr *name)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(dir),
    				_nfs4_proc_remove(dir, name),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    
    static void nfs4_proc_unlink_setup(struct rpc_message *msg, struct inode *dir)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct nfs_removeargs *args = msg->rpc_argp;
    	struct nfs_removeres *res = msg->rpc_resp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	args->bitmask = server->cache_consistency_bitmask;
    
    	res->server = server;
    
    	res->seq_res.sr_slot = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_REMOVE];
    }
    
    
    static int nfs4_proc_unlink_done(struct rpc_task *task, struct inode *dir)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_removeres *res = task->tk_msg.rpc_resp;
    
    
    	if (!nfs4_sequence_done(task, &res->seq_res))
    		return 0;
    
    	if (nfs4_async_handle_error(task, res->server, NULL) == -EAGAIN)
    
    		return 0;
    	update_changeattr(dir, &res->cinfo);
    
    	nfs_post_op_update_inode(dir, res->dir_attr);
    
    static void nfs4_proc_rename_setup(struct rpc_message *msg, struct inode *dir)
    {
    	struct nfs_server *server = NFS_SERVER(dir);
    	struct nfs_renameargs *arg = msg->rpc_argp;
    	struct nfs_renameres *res = msg->rpc_resp;
    
    	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENAME];
    	arg->bitmask = server->attr_bitmask;
    	res->server = server;
    }
    
    static int nfs4_proc_rename_done(struct rpc_task *task, struct inode *old_dir,
    				 struct inode *new_dir)
    {
    	struct nfs_renameres *res = task->tk_msg.rpc_resp;
    
    	if (!nfs4_sequence_done(task, &res->seq_res))
    		return 0;
    	if (nfs4_async_handle_error(task, res->server, NULL) == -EAGAIN)
    		return 0;
    
    	update_changeattr(old_dir, &res->old_cinfo);
    	nfs_post_op_update_inode(old_dir, res->old_fattr);
    	update_changeattr(new_dir, &res->new_cinfo);
    	nfs_post_op_update_inode(new_dir, res->new_fattr);
    	return 1;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int _nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name,
    		struct inode *new_dir, struct qstr *new_name)
    {
    
    	struct nfs_server *server = NFS_SERVER(old_dir);
    
    	struct nfs_renameargs arg = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.old_dir = NFS_FH(old_dir),
    		.new_dir = NFS_FH(new_dir),
    		.old_name = old_name,
    		.new_name = new_name,
    
    		.bitmask = server->attr_bitmask,
    	};
    
    	struct nfs_renameres res = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENAME],
    		.rpc_argp = &arg,
    		.rpc_resp = &res,
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	
    
    	res.old_fattr = nfs_alloc_fattr();
    	res.new_fattr = nfs_alloc_fattr();
    	if (res.old_fattr == NULL || res.new_fattr == NULL)
    		goto out;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	status = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!status) {
    		update_changeattr(old_dir, &res.old_cinfo);
    
    		nfs_post_op_update_inode(old_dir, res.old_fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		update_changeattr(new_dir, &res.new_cinfo);
    
    		nfs_post_op_update_inode(new_dir, res.new_fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    out:
    	nfs_free_fattr(res.new_fattr);
    	nfs_free_fattr(res.old_fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    static int nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name,
    		struct inode *new_dir, struct qstr *new_name)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(old_dir),
    				_nfs4_proc_rename(old_dir, old_name,
    					new_dir, new_name),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_proc_link(struct inode *inode, struct inode *dir, struct qstr *name)
    {
    
    	struct nfs_server *server = NFS_SERVER(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct nfs4_link_arg arg = {
    		.fh     = NFS_FH(inode),
    		.dir_fh = NFS_FH(dir),
    		.name   = name,
    
    		.bitmask = server->attr_bitmask,
    	};
    	struct nfs4_link_res res = {
    		.server = server,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LINK],
    		.rpc_argp = &arg,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	int status = -ENOMEM;
    
    	res.fattr = nfs_alloc_fattr();
    	res.dir_attr = nfs_alloc_fattr();
    	if (res.fattr == NULL || res.dir_attr == NULL)
    		goto out;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	status = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
    
    	if (!status) {
    		update_changeattr(dir, &res.cinfo);
    		nfs_post_op_update_inode(dir, res.dir_attr);
    
    		nfs_post_op_update_inode(inode, res.fattr);
    
    out:
    	nfs_free_fattr(res.dir_attr);
    	nfs_free_fattr(res.fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    static int nfs4_proc_link(struct inode *inode, struct inode *dir, struct qstr *name)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(inode),
    				_nfs4_proc_link(inode, dir, name),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    
    struct nfs4_createdata {
    	struct rpc_message msg;
    	struct nfs4_create_arg arg;
    	struct nfs4_create_res res;
    	struct nfs_fh fh;
    	struct nfs_fattr fattr;
    	struct nfs_fattr dir_fattr;
    };
    
    static struct nfs4_createdata *nfs4_alloc_createdata(struct inode *dir,
    		struct qstr *name, struct iattr *sattr, u32 ftype)
    {
    	struct nfs4_createdata *data;
    
    	data = kzalloc(sizeof(*data), GFP_KERNEL);
    	if (data != NULL) {
    		struct nfs_server *server = NFS_SERVER(dir);
    
    		data->msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CREATE];
    		data->msg.rpc_argp = &data->arg;
    		data->msg.rpc_resp = &data->res;
    		data->arg.dir_fh = NFS_FH(dir);
    		data->arg.server = server;
    		data->arg.name = name;
    		data->arg.attrs = sattr;
    		data->arg.ftype = ftype;
    		data->arg.bitmask = server->attr_bitmask;
    		data->res.server = server;
    		data->res.fh = &data->fh;
    		data->res.fattr = &data->fattr;
    		data->res.dir_fattr = &data->dir_fattr;
    		nfs_fattr_init(data->res.fattr);
    		nfs_fattr_init(data->res.dir_fattr);
    	}
    	return data;
    }
    
    static int nfs4_do_create(struct inode *dir, struct dentry *dentry, struct nfs4_createdata *data)
    {
    
    	int status = nfs4_call_sync(NFS_SERVER(dir)->client, NFS_SERVER(dir), &data->msg,
    
    				    &data->arg.seq_args, &data->res.seq_res, 1);
    
    	if (status == 0) {
    		update_changeattr(dir, &data->res.dir_cinfo);
    		nfs_post_op_update_inode(dir, data->res.dir_fattr);
    		status = nfs_instantiate(dentry, data->res.fh, data->res.fattr);
    	}
    	return status;
    }
    
    static void nfs4_free_createdata(struct nfs4_createdata *data)
    {
    	kfree(data);
    }
    
    
    static int _nfs4_proc_symlink(struct inode *dir, struct dentry *dentry,
    
    		struct page *page, unsigned int len, struct iattr *sattr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_createdata *data;
    	int status = -ENAMETOOLONG;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (len > NFS4_MAXPATHLEN)
    
    	status = -ENOMEM;
    	data = nfs4_alloc_createdata(dir, &dentry->d_name, sattr, NF4LNK);
    	if (data == NULL)
    		goto out;
    
    	data->msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SYMLINK];
    	data->arg.u.symlink.pages = &page;
    	data->arg.u.symlink.len = len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	
    
    	status = nfs4_do_create(dir, dentry, data);
    
    	nfs4_free_createdata(data);
    out:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    
    static int nfs4_proc_symlink(struct inode *dir, struct dentry *dentry,
    
    		struct page *page, unsigned int len, struct iattr *sattr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(dir),
    
    				_nfs4_proc_symlink(dir, dentry, page,
    							len, sattr),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry,
    		struct iattr *sattr)
    {
    
    	struct nfs4_createdata *data;
    	int status = -ENOMEM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	data = nfs4_alloc_createdata(dir, &dentry->d_name, sattr, NF4DIR);
    	if (data == NULL)
    		goto out;
    
    	status = nfs4_do_create(dir, dentry, data);
    
    	nfs4_free_createdata(data);
    out:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    static int nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry,
    		struct iattr *sattr)
    {
    	struct nfs4_exception exception = { };
    	int err;
    
    
    	sattr->ia_mode &= ~current_umask();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(dir),
    				_nfs4_proc_mkdir(dir, dentry, sattr),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_proc_readdir(struct dentry *dentry, struct rpc_cred *cred,
    
    		u64 cookie, struct page **pages, unsigned int count, int plus)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inode		*dir = dentry->d_inode;
    	struct nfs4_readdir_arg args = {
    		.fh = NFS_FH(dir),
    
    		.pages = pages,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.pgbase = 0,
    		.count = count,
    
    		.bitmask = NFS_SERVER(dentry->d_inode)->attr_bitmask,
    
    Bryan Schumaker's avatar
    Bryan Schumaker committed
    		.plus = plus,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct nfs4_readdir_res res;
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READDIR],
    		.rpc_argp = &args,
    		.rpc_resp = &res,
    		.rpc_cred = cred,
    	};
    	int			status;
    
    
    	dprintk("%s: dentry = %s/%s, cookie = %Lu\n", __func__,
    
    			dentry->d_parent->d_name.name,
    			dentry->d_name.name,
    			(unsigned long long)cookie);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	nfs4_setup_readdir(cookie, NFS_COOKIEVERF(dir), dentry, &args);
    	res.pgbase = args.pgbase;
    
    	status = nfs4_call_sync(NFS_SERVER(dir)->client, NFS_SERVER(dir), &msg, &args.seq_args, &res.seq_res, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		memcpy(NFS_COOKIEVERF(dir), res.verifier.data, NFS4_VERIFIER_SIZE);
    
    
    	nfs_invalidate_atime(dir);