Skip to content
Snippets Groups Projects
nfs4proc.c 161 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	dprintk("%s: returns %d\n", __func__, status);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    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 nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(dentry->d_inode),
    				_nfs4_proc_readdir(dentry, cred, cookie,
    
    					pages, count, plus),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_proc_mknod(struct inode *dir, struct dentry *dentry,
    		struct iattr *sattr, dev_t rdev)
    {
    
    	struct nfs4_createdata *data;
    	int mode = sattr->ia_mode;
    	int status = -ENOMEM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	BUG_ON(!(sattr->ia_valid & ATTR_MODE));
    	BUG_ON(!S_ISFIFO(mode) && !S_ISBLK(mode) && !S_ISCHR(mode) && !S_ISSOCK(mode));
    
    
    	data = nfs4_alloc_createdata(dir, &dentry->d_name, sattr, NF4SOCK);
    	if (data == NULL)
    		goto out;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (S_ISFIFO(mode))
    
    		data->arg.ftype = NF4FIFO;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else if (S_ISBLK(mode)) {
    
    		data->arg.ftype = NF4BLK;
    		data->arg.u.device.specdata1 = MAJOR(rdev);
    		data->arg.u.device.specdata2 = MINOR(rdev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	else if (S_ISCHR(mode)) {
    
    		data->arg.ftype = NF4CHR;
    		data->arg.u.device.specdata1 = MAJOR(rdev);
    		data->arg.u.device.specdata2 = MINOR(rdev);
    
    	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_mknod(struct inode *dir, struct dentry *dentry,
    		struct iattr *sattr, dev_t rdev)
    {
    	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_mknod(dir, dentry, sattr, rdev),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle,
    		 struct nfs_fsstat *fsstat)
    {
    	struct nfs4_statfs_arg args = {
    		.fh = fhandle,
    		.bitmask = server->attr_bitmask,
    	};
    
    Benny Halevy's avatar
    Benny Halevy committed
    	struct nfs4_statfs_res res = {
    		.fsstat = fsstat,
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_STATFS],
    		.rpc_argp = &args,
    
    Benny Halevy's avatar
    Benny Halevy committed
    		.rpc_resp = &res,
    
    	nfs_fattr_init(fsstat->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_statfs(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fsstat *fsstat)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(server,
    				_nfs4_proc_statfs(server, fhandle, fsstat),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int _nfs4_do_fsinfo(struct nfs_server *server, struct nfs_fh *fhandle,
    		struct nfs_fsinfo *fsinfo)
    {
    	struct nfs4_fsinfo_arg args = {
    		.fh = fhandle,
    		.bitmask = server->attr_bitmask,
    	};
    
    Benny Halevy's avatar
    Benny Halevy committed
    	struct nfs4_fsinfo_res res = {
    		.fsinfo = fsinfo,
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_FSINFO],
    		.rpc_argp = &args,
    
    Benny Halevy's avatar
    Benny Halevy committed
    		.rpc_resp = &res,
    
    	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_do_fsinfo(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fsinfo *fsinfo)
    {
    	struct nfs4_exception exception = { };
    	int err;
    
    	do {
    		err = nfs4_handle_exception(server,
    				_nfs4_do_fsinfo(server, fhandle, fsinfo),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    static int nfs4_proc_fsinfo(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fsinfo *fsinfo)
    {
    
    	nfs_fattr_init(fsinfo->fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return nfs4_do_fsinfo(server, fhandle, fsinfo);
    }
    
    static int _nfs4_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle,
    		struct nfs_pathconf *pathconf)
    {
    	struct nfs4_pathconf_arg args = {
    		.fh = fhandle,
    		.bitmask = server->attr_bitmask,
    	};
    
    	struct nfs4_pathconf_res res = {
    		.pathconf = pathconf,
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_PATHCONF],
    		.rpc_argp = &args,
    
    		.rpc_resp = &res,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	/* None of the pathconf attributes are mandatory to implement */
    	if ((args.bitmask[0] & nfs4_pathconf_bitmap[0]) == 0) {
    		memset(pathconf, 0, sizeof(*pathconf));
    		return 0;
    	}
    
    
    	nfs_fattr_init(pathconf->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_pathconf(struct nfs_server *server, struct nfs_fh *fhandle,
    		struct nfs_pathconf *pathconf)
    {
    	struct nfs4_exception exception = { };
    	int err;
    
    	do {
    		err = nfs4_handle_exception(server,
    				_nfs4_proc_pathconf(server, fhandle, pathconf),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    
    void __nfs4_read_done_cb(struct nfs_read_data *data)
    {
    	nfs_invalidate_atime(data->inode);
    }
    
    
    static int nfs4_read_done_cb(struct rpc_task *task, struct nfs_read_data *data)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_server *server = NFS_SERVER(data->inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (nfs4_async_handle_error(task, server, data->args.context->state) == -EAGAIN) {
    
    		nfs_restart_rpc(task, server->nfs_client);
    
    		return -EAGAIN;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	__nfs4_read_done_cb(data);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (task->tk_status > 0)
    
    		renew_lease(server, data->timestamp);
    	return 0;
    
    static int nfs4_read_done(struct rpc_task *task, struct nfs_read_data *data)
    {
    
    	dprintk("--> %s\n", __func__);
    
    	if (!nfs4_sequence_done(task, &data->res.seq_res))
    		return -EAGAIN;
    
    
    	return data->read_done_cb ? data->read_done_cb(task, data) :
    				    nfs4_read_done_cb(task, data);
    
    static void nfs4_proc_read_setup(struct nfs_read_data *data, struct rpc_message *msg)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	data->timestamp   = jiffies;
    
    	data->read_done_cb = nfs4_read_done_cb;
    
    	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READ];
    
    /* Reset the the nfs_read_data to send the read to the MDS. */
    void nfs4_reset_read(struct rpc_task *task, struct nfs_read_data *data)
    {
    	dprintk("%s Reset task for i/o through\n", __func__);
    	put_lseg(data->lseg);
    	data->lseg = NULL;
    	/* offsets will differ in the dense stripe case */
    	data->args.offset = data->mds_offset;
    	data->ds_clp = NULL;
    	data->args.fh     = NFS_FH(data->inode);
    	data->read_done_cb = nfs4_read_done_cb;
    	task->tk_ops = data->mds_ops;
    	rpc_task_reset_client(task, NFS_CLIENT(data->inode));
    }
    EXPORT_SYMBOL_GPL(nfs4_reset_read);
    
    
    static int nfs4_write_done_cb(struct rpc_task *task, struct nfs_write_data *data)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inode *inode = data->inode;
    	
    
    	if (nfs4_async_handle_error(task, NFS_SERVER(inode), data->args.context->state) == -EAGAIN) {
    
    		nfs_restart_rpc(task, NFS_SERVER(inode)->nfs_client);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		renew_lease(NFS_SERVER(inode), data->timestamp);
    
    		nfs_post_op_update_inode_force_wcc(inode, data->res.fattr);
    
    static int nfs4_write_done(struct rpc_task *task, struct nfs_write_data *data)
    {
    	if (!nfs4_sequence_done(task, &data->res.seq_res))
    		return -EAGAIN;
    
    	return data->write_done_cb ? data->write_done_cb(task, data) :
    		nfs4_write_done_cb(task, data);
    
    /* Reset the the nfs_write_data to send the write to the MDS. */
    void nfs4_reset_write(struct rpc_task *task, struct nfs_write_data *data)
    {
    	dprintk("%s Reset task for i/o through\n", __func__);
    	put_lseg(data->lseg);
    	data->lseg          = NULL;
    	data->ds_clp        = NULL;
    	data->write_done_cb = nfs4_write_done_cb;
    	data->args.fh       = NFS_FH(data->inode);
    	data->args.bitmask  = data->res.server->cache_consistency_bitmask;
    	data->args.offset   = data->mds_offset;
    	data->res.fattr     = &data->fattr;
    	task->tk_ops        = data->mds_ops;
    	rpc_task_reset_client(task, NFS_CLIENT(data->inode));
    }
    EXPORT_SYMBOL_GPL(nfs4_reset_write);
    
    
    static void nfs4_proc_write_setup(struct nfs_write_data *data, struct rpc_message *msg)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_server *server = NFS_SERVER(data->inode);
    
    
    	if (data->lseg) {
    		data->args.bitmask = NULL;
    		data->res.fattr = NULL;
    	} else
    		data->args.bitmask = server->cache_consistency_bitmask;
    
    	if (!data->write_done_cb)
    		data->write_done_cb = nfs4_write_done_cb;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	data->timestamp   = jiffies;
    
    
    	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_WRITE];
    
    static int nfs4_commit_done_cb(struct rpc_task *task, struct nfs_write_data *data)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inode *inode = data->inode;
    
    	if (nfs4_async_handle_error(task, NFS_SERVER(inode), NULL) == -EAGAIN) {
    
    		nfs_restart_rpc(task, NFS_SERVER(inode)->nfs_client);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	nfs_refresh_inode(inode, data->res.fattr);
    
    static int nfs4_commit_done(struct rpc_task *task, struct nfs_write_data *data)
    {
    	if (!nfs4_sequence_done(task, &data->res.seq_res))
    		return -EAGAIN;
    	return data->write_done_cb(task, data);
    }
    
    
    static void nfs4_proc_commit_setup(struct nfs_write_data *data, struct rpc_message *msg)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_server *server = NFS_SERVER(data->inode);
    
    
    	if (data->lseg) {
    		data->args.bitmask = NULL;
    		data->res.fattr = NULL;
    	} else
    		data->args.bitmask = server->cache_consistency_bitmask;
    
    	if (!data->write_done_cb)
    		data->write_done_cb = nfs4_commit_done_cb;
    
    	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_COMMIT];
    
    struct nfs4_renewdata {
    	struct nfs_client	*client;
    	unsigned long		timestamp;
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * nfs4_proc_async_renew(): This is not one of the nfs_rpc_ops; it is a special
     * standalone procedure for queueing an asynchronous RENEW.
     */
    
    static void nfs4_renew_release(void *calldata)
    
    	struct nfs4_renewdata *data = calldata;
    	struct nfs_client *clp = data->client;
    
    	if (atomic_read(&clp->cl_count) > 1)
    		nfs4_schedule_state_renewal(clp);
    	nfs_put_client(clp);
    
    	kfree(data);
    
    static void nfs4_renew_done(struct rpc_task *task, void *calldata)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs4_renewdata *data = calldata;
    	struct nfs_client *clp = data->client;
    	unsigned long timestamp = data->timestamp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (task->tk_status < 0) {
    
    		/* Unless we're shutting down, schedule state recovery! */
    		if (test_bit(NFS_CS_RENEWD, &clp->cl_res_state) != 0)
    
    			nfs4_schedule_lease_recovery(clp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return;
    	}
    
    	do_renew_lease(clp, timestamp);
    
    static const struct rpc_call_ops nfs4_renew_ops = {
    	.rpc_call_done = nfs4_renew_done,
    
    	.rpc_release = nfs4_renew_release,
    
    int nfs4_proc_async_renew(struct nfs_client *clp, struct rpc_cred *cred)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct rpc_message msg = {
    		.rpc_proc	= &nfs4_procedures[NFSPROC4_CLNT_RENEW],
    		.rpc_argp	= clp,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	struct nfs4_renewdata *data;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (!atomic_inc_not_zero(&clp->cl_count))
    		return -EIO;
    
    	data = kmalloc(sizeof(*data), GFP_KERNEL);
    	if (data == NULL)
    		return -ENOMEM;
    	data->client = clp;
    	data->timestamp = jiffies;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return rpc_call_async(clp->cl_rpcclient, &msg, RPC_TASK_SOFT,
    
    			&nfs4_renew_ops, data);
    
    int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct rpc_message msg = {
    		.rpc_proc	= &nfs4_procedures[NFSPROC4_CLNT_RENEW],
    		.rpc_argp	= clp,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	unsigned long now = jiffies;
    	int status;
    
    	status = rpc_call_sync(clp->cl_rpcclient, &msg, 0);
    	if (status < 0)
    		return status;
    
    	do_renew_lease(clp, now);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    
    static inline int nfs4_server_supports_acls(struct nfs_server *server)
    {
    	return (server->caps & NFS_CAP_ACLS)
    		&& (server->acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
    		&& (server->acl_bitmask & ACL4_SUPPORT_DENY_ACL);
    }
    
    /* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_CACHE_SIZE, and that
     * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_CACHE_SIZE) bytes on
     * the stack.
     */
    #define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT)
    
    static void buf_to_pages(const void *buf, size_t buflen,
    		struct page **pages, unsigned int *pgbase)
    {
    	const void *p = buf;
    
    	*pgbase = offset_in_page(buf);
    	p -= *pgbase;
    	while (p < buf + buflen) {
    		*(pages++) = virt_to_page(p);
    		p += PAGE_CACHE_SIZE;
    	}
    }
    
    
    static int buf_to_pages_noslab(const void *buf, size_t buflen,
    		struct page **pages, unsigned int *pgbase)
    {
    	struct page *newpage, **spages;
    	int rc = 0;
    	size_t len;
    	spages = pages;
    
    	do {
    
    Jovi Zhang's avatar
    Jovi Zhang committed
    		len = min_t(size_t, PAGE_CACHE_SIZE, buflen);
    
    		newpage = alloc_page(GFP_KERNEL);
    
    		if (newpage == NULL)
    			goto unwind;
    		memcpy(page_address(newpage), buf, len);
                    buf += len;
                    buflen -= len;
    		*pages++ = newpage;
    		rc++;
    	} while (buflen != 0);
    
    	return rc;
    
    unwind:
    	for(; rc > 0; rc--)
    		__free_page(spages[rc-1]);
    	return -ENOMEM;
    }
    
    
    struct nfs4_cached_acl {
    	int cached;
    	size_t len;
    
    	char data[0];
    
    };
    
    static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	spin_lock(&inode->i_lock);
    	kfree(nfsi->nfs4_acl);
    	nfsi->nfs4_acl = acl;
    	spin_unlock(&inode->i_lock);
    }
    
    static void nfs4_zap_acl_attr(struct inode *inode)
    {
    	nfs4_set_cached_acl(inode, NULL);
    }
    
    static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    	struct nfs4_cached_acl *acl;
    	int ret = -ENOENT;
    
    	spin_lock(&inode->i_lock);
    	acl = nfsi->nfs4_acl;
    	if (acl == NULL)
    		goto out;
    	if (buf == NULL) /* user is just asking for length */
    		goto out_len;
    	if (acl->cached == 0)
    		goto out;
    	ret = -ERANGE; /* see getxattr(2) man page */
    	if (acl->len > buflen)
    		goto out;
    	memcpy(buf, acl->data, acl->len);
    out_len:
    	ret = acl->len;
    out:
    	spin_unlock(&inode->i_lock);
    	return ret;
    }
    
    static void nfs4_write_cached_acl(struct inode *inode, const char *buf, size_t acl_len)
    {
    	struct nfs4_cached_acl *acl;
    
    	if (buf && acl_len <= PAGE_SIZE) {
    		acl = kmalloc(sizeof(*acl) + acl_len, GFP_KERNEL);
    		if (acl == NULL)
    			goto out;
    		acl->cached = 1;
    		memcpy(acl->data, buf, acl_len);
    	} else {
    		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
    		if (acl == NULL)
    			goto out;
    		acl->cached = 0;
    	}
    	acl->len = acl_len;
    out:
    	nfs4_set_cached_acl(inode, acl);
    }
    
    
    static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
    
    {
    	struct page *pages[NFS4ACL_MAXPAGES];
    	struct nfs_getaclargs args = {
    		.fh = NFS_FH(inode),
    		.acl_pages = pages,
    		.acl_len = buflen,
    	};
    
    Benny Halevy's avatar
    Benny Halevy committed
    	struct nfs_getaclres res = {
    		.acl_len = buflen,
    	};
    
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
    		.rpc_argp = &args,
    
    Benny Halevy's avatar
    Benny Halevy committed
    		.rpc_resp = &res,
    
    	struct page *localpage = NULL;
    
    	if (buflen < PAGE_SIZE) {
    		/* As long as we're doing a round trip to the server anyway,
    		 * let's be prepared for a page of acl data. */
    		localpage = alloc_page(GFP_KERNEL);
    		resp_buf = page_address(localpage);
    		if (localpage == NULL)
    			return -ENOMEM;
    		args.acl_pages[0] = localpage;
    		args.acl_pgbase = 0;
    
    Benny Halevy's avatar
    Benny Halevy committed
    		args.acl_len = PAGE_SIZE;
    
    	} else {
    		resp_buf = buf;
    		buf_to_pages(buf, buflen, args.acl_pages, &args.acl_pgbase);
    	}
    
    	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode), &msg, &args.seq_args, &res.seq_res, 0);
    
    Benny Halevy's avatar
    Benny Halevy committed
    	if (res.acl_len > args.acl_len)
    		nfs4_write_cached_acl(inode, NULL, res.acl_len);
    
    Benny Halevy's avatar
    Benny Halevy committed
    		nfs4_write_cached_acl(inode, resp_buf, res.acl_len);
    
    	if (buf) {
    		ret = -ERANGE;
    
    Benny Halevy's avatar
    Benny Halevy committed
    		if (res.acl_len > buflen)
    
    			goto out_free;
    		if (localpage)
    
    Benny Halevy's avatar
    Benny Halevy committed
    			memcpy(buf, resp_buf, res.acl_len);
    
    Benny Halevy's avatar
    Benny Halevy committed
    	ret = res.acl_len;
    
    out_free:
    	if (localpage)
    		__free_page(localpage);
    
    static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
    {
    	struct nfs4_exception exception = { };
    	ssize_t ret;
    	do {
    		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
    		if (ret >= 0)
    			break;
    		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
    	} while (exception.retry);
    	return ret;
    }
    
    
    static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
    {
    	struct nfs_server *server = NFS_SERVER(inode);
    	int ret;
    
    	if (!nfs4_server_supports_acls(server))
    		return -EOPNOTSUPP;
    	ret = nfs_revalidate_inode(server, inode);
    	if (ret < 0)
    		return ret;
    
    	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
    		nfs_zap_acl_cache(inode);
    
    	ret = nfs4_read_cached_acl(inode, buf, buflen);
    	if (ret != -ENOENT)
    		return ret;
    	return nfs4_get_acl_uncached(inode, buf, buflen);
    }
    
    
    static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
    
    {
    	struct nfs_server *server = NFS_SERVER(inode);
    	struct page *pages[NFS4ACL_MAXPAGES];
    	struct nfs_setaclargs arg = {
    		.fh		= NFS_FH(inode),
    		.acl_pages	= pages,
    		.acl_len	= buflen,
    	};
    
    Benny Halevy's avatar
    Benny Halevy committed
    	struct nfs_setaclres res;
    
    	struct rpc_message msg = {
    		.rpc_proc	= &nfs4_procedures[NFSPROC4_CLNT_SETACL],
    		.rpc_argp	= &arg,
    
    Benny Halevy's avatar
    Benny Halevy committed
    		.rpc_resp	= &res,
    
    
    	if (!nfs4_server_supports_acls(server))
    		return -EOPNOTSUPP;
    
    	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages, &arg.acl_pgbase);
    	if (i < 0)
    		return i;
    
    	nfs_inode_return_delegation(inode);
    
    	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
    
    
    	/*
    	 * Free each page after tx, so the only ref left is
    	 * held by the network stack
    	 */
    	for (; i > 0; i--)
    		put_page(pages[i-1]);
    
    
    	/*
    	 * Acl update can result in inode attribute update.
    	 * so mark the attribute cache invalid.
    	 */
    	spin_lock(&inode->i_lock);
    	NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ATTR;
    	spin_unlock(&inode->i_lock);
    
    	nfs_access_zap_cache(inode);
    	nfs_zap_acl_cache(inode);
    
    static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
    {
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(inode),
    				__nfs4_proc_set_acl(inode, buf, buflen),
    				&exception);
    	} while (exception.retry);
    	return err;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int
    
    nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server, struct nfs4_state *state)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct nfs_client *clp = server->nfs_client;
    
    	if (task->tk_status >= 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return 0;
    	switch(task->tk_status) {
    
    		case -NFS4ERR_ADMIN_REVOKED:
    		case -NFS4ERR_BAD_STATEID:
    		case -NFS4ERR_OPENMODE:
    			if (state == NULL)
    				break;
    
    			nfs4_schedule_stateid_recovery(server, state);
    			goto wait_on_recovery;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		case -NFS4ERR_STALE_STATEID:
    
    		case -NFS4ERR_STALE_CLIENTID:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		case -NFS4ERR_EXPIRED:
    
    			nfs4_schedule_lease_recovery(clp);
    			goto wait_on_recovery;
    
    #if defined(CONFIG_NFS_V4_1)
    		case -NFS4ERR_BADSESSION:
    		case -NFS4ERR_BADSLOT:
    		case -NFS4ERR_BAD_HIGH_SLOT:
    		case -NFS4ERR_DEADSESSION:
    		case -NFS4ERR_CONN_NOT_BOUND_TO_SESSION:
    		case -NFS4ERR_SEQ_FALSE_RETRY:
    		case -NFS4ERR_SEQ_MISORDERED:
    			dprintk("%s ERROR %d, Reset session\n", __func__,
    				task->tk_status);
    
    			nfs4_schedule_session_recovery(clp->cl_session);
    
    			task->tk_status = 0;
    			return -EAGAIN;
    #endif /* CONFIG_NFS_V4_1 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		case -NFS4ERR_DELAY:
    
    			nfs_inc_server_stats(server, NFSIOS_DELAY);
    
    		case -NFS4ERR_GRACE:
    
    		case -EKEYEXPIRED:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			rpc_delay(task, NFS4_POLL_RETRY_MAX);
    			task->tk_status = 0;
    			return -EAGAIN;
    
    		case -NFS4ERR_RETRY_UNCACHED_REP:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		case -NFS4ERR_OLD_STATEID:
    			task->tk_status = 0;
    			return -EAGAIN;
    	}
    	task->tk_status = nfs4_map_errors(task->tk_status);
    	return 0;
    
    	rpc_sleep_on(&clp->cl_rpcwaitq, task, NULL);
    	if (test_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state) == 0)
    		rpc_wake_up_queued_task(&clp->cl_rpcwaitq, task);
    	task->tk_status = 0;
    	return -EAGAIN;
    
    int nfs4_proc_setclientid(struct nfs_client *clp, u32 program,
    		unsigned short port, struct rpc_cred *cred,
    		struct nfs4_setclientid_res *res)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	nfs4_verifier sc_verifier;
    	struct nfs4_setclientid setclientid = {
    		.sc_verifier = &sc_verifier,
    		.sc_prog = program,
    
    		.sc_cb_ident = clp->cl_cb_ident,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETCLIENTID],
    		.rpc_argp = &setclientid,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	__be32 *p;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int loop = 0;
    	int status;
    
    
    	p = (__be32*)sc_verifier.data;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	*p++ = htonl((u32)clp->cl_boot_time.tv_sec);
    	*p = htonl((u32)clp->cl_boot_time.tv_nsec);
    
    	for(;;) {
    		setclientid.sc_name_len = scnprintf(setclientid.sc_name,
    
    				sizeof(setclientid.sc_name), "%s/%s %s %s %u",
    
    				clp->cl_ipaddr,
    				rpc_peeraddr2str(clp->cl_rpcclient,
    							RPC_DISPLAY_ADDR),
    
    				rpc_peeraddr2str(clp->cl_rpcclient,
    							RPC_DISPLAY_PROTO),
    
    				clp->cl_rpcclient->cl_auth->au_ops->au_name,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				clp->cl_id_uniquifier);
    		setclientid.sc_netid_len = scnprintf(setclientid.sc_netid,
    
    				sizeof(setclientid.sc_netid),
    				rpc_peeraddr2str(clp->cl_rpcclient,
    							RPC_DISPLAY_NETID));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		setclientid.sc_uaddr_len = scnprintf(setclientid.sc_uaddr,
    
    				sizeof(setclientid.sc_uaddr), "%s.%u.%u",
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				clp->cl_ipaddr, port >> 8, port & 255);
    
    
    		status = rpc_call_sync(clp->cl_rpcclient, &msg, RPC_TASK_TIMEOUT);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (status != -NFS4ERR_CLID_INUSE)
    			break;
    
    		if (loop != 0) {
    			++clp->cl_id_uniquifier;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			break;
    
    		}
    		++loop;
    		ssleep(clp->cl_lease_time / HZ + 1);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return status;
    }
    
    
    int nfs4_proc_setclientid_confirm(struct nfs_client *clp,
    
    		struct nfs4_setclientid_res *arg,
    		struct rpc_cred *cred)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_fsinfo fsinfo;
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETCLIENTID_CONFIRM],
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.rpc_resp = &fsinfo,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	unsigned long now;
    	int status;
    
    	now = jiffies;
    
    	status = rpc_call_sync(clp->cl_rpcclient, &msg, RPC_TASK_TIMEOUT);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status == 0) {
    		spin_lock(&clp->cl_lock);
    		clp->cl_lease_time = fsinfo.lease_time * HZ;
    		clp->cl_last_renewal = now;
    		spin_unlock(&clp->cl_lock);
    	}
    	return status;
    }
    
    
    struct nfs4_delegreturndata {
    	struct nfs4_delegreturnargs args;
    
    	struct nfs4_delegreturnres res;
    
    	struct nfs_fh fh;
    	nfs4_stateid stateid;
    
    	struct nfs_fattr fattr;
    
    	int rpc_status;
    };
    
    static void nfs4_delegreturn_done(struct rpc_task *task, void *calldata)
    {
    	struct nfs4_delegreturndata *data = calldata;
    
    	if (!nfs4_sequence_done(task, &data->res.seq_res))
    		return;
    
    	switch (task->tk_status) {
    	case -NFS4ERR_STALE_STATEID:
    	case -NFS4ERR_EXPIRED:
    	case 0:
    
    		renew_lease(data->res.server, data->timestamp);
    
    		break;
    	default:
    		if (nfs4_async_handle_error(task, data->res.server, NULL) ==
    				-EAGAIN) {
    			nfs_restart_rpc(task, data->res.server->nfs_client);
    			return;
    		}
    	}
    	data->rpc_status = task->tk_status;
    
    }
    
    static void nfs4_delegreturn_release(void *calldata)
    {
    	kfree(calldata);
    }
    
    
    #if defined(CONFIG_NFS_V4_1)
    static void nfs4_delegreturn_prepare(struct rpc_task *task, void *data)
    {
    	struct nfs4_delegreturndata *d_data;
    
    	d_data = (struct nfs4_delegreturndata *)data;
    
    
    	if (nfs4_setup_sequence(d_data->res.server,
    
    				&d_data->args.seq_args,
    				&d_data->res.seq_res, 1, task))
    		return;
    	rpc_call_start(task);
    }
    #endif /* CONFIG_NFS_V4_1 */
    
    
    static const struct rpc_call_ops nfs4_delegreturn_ops = {
    
    #if defined(CONFIG_NFS_V4_1)
    	.rpc_call_prepare = nfs4_delegreturn_prepare,
    #endif /* CONFIG_NFS_V4_1 */
    
    	.rpc_call_done = nfs4_delegreturn_done,
    	.rpc_release = nfs4_delegreturn_release,
    };
    
    
    static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid, int issync)
    
    {
    	struct nfs4_delegreturndata *data;
    
    	struct nfs_server *server = NFS_SERVER(inode);
    
    	struct rpc_message msg = {
    		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_DELEGRETURN],
    		.rpc_cred = cred,
    	};
    
    	struct rpc_task_setup task_setup_data = {
    		.rpc_client = server->client,
    
    		.callback_ops = &nfs4_delegreturn_ops,
    		.flags = RPC_TASK_ASYNC,
    	};
    
    	data = kzalloc(sizeof(*data), GFP_NOFS);
    
    	if (data == NULL)
    		return -ENOMEM;
    	data->args.fhandle = &data->fh;
    	data->args.stateid = &data->stateid;
    
    	data->args.bitmask = server->attr_bitmask;
    
    	nfs_copy_fh(&data->fh, NFS_FH(inode));
    	memcpy(&data->stateid, stateid, sizeof(data->stateid));
    
    	data->res.fattr = &data->fattr;
    	data->res.server = server;
    
    	nfs_fattr_init(data->res.fattr);
    
    	task_setup_data.callback_data = data;
    
    	msg.rpc_argp = &data->args;
    	msg.rpc_resp = &data->res;
    
    	task = rpc_run_task(&task_setup_data);
    
    	status = nfs4_wait_for_completion_rpc_task(task);
    
    	if (status != 0)
    		goto out;
    	status = data->rpc_status;
    	if (status != 0)
    		goto out;
    	nfs_refresh_inode(inode, &data->fattr);
    out:
    
    	rpc_put_task(task);
    
    int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid, int issync)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_server *server = NFS_SERVER(inode);
    	struct nfs4_exception exception = { };
    	int err;
    	do {
    
    		err = _nfs4_proc_delegreturn(inode, cred, stateid, issync);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		switch (err) {
    			case -NFS4ERR_STALE_STATEID:
    			case -NFS4ERR_EXPIRED:
    			case 0:
    				return 0;
    		}
    		err = nfs4_handle_exception(server, err, &exception);
    	} while (exception.retry);
    	return err;
    }
    
    #define NFS4_LOCK_MINTIMEOUT (1 * HZ)
    #define NFS4_LOCK_MAXTIMEOUT (30 * HZ)
    
    /* 
     * sleep, with exponential backoff, and retry the LOCK operation. 
     */
    static unsigned long
    nfs4_set_lock_task_retry(unsigned long timeout)
    {
    
    	schedule_timeout_killable(timeout);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	timeout <<= 1;
    	if (timeout > NFS4_LOCK_MAXTIMEOUT)
    		return NFS4_LOCK_MAXTIMEOUT;
    	return timeout;
    }
    
    static int _nfs4_proc_getlk(struct nfs4_state *state, int cmd, struct file_lock *request)
    {
    	struct inode *inode = state->inode;
    	struct nfs_server *server = NFS_SERVER(inode);
    
    	struct nfs_client *clp = server->nfs_client;
    
    	struct nfs_lockt_args arg = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		.fh = NFS_FH(inode),
    
    		.fl = request,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    
    	struct nfs_lockt_res res = {
    		.denied = request,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	};
    	struct rpc_message msg = {
    		.rpc_proc	= &nfs4_procedures[NFSPROC4_CLNT_LOCKT],
    		.rpc_argp       = &arg,
    		.rpc_resp       = &res,
    		.rpc_cred	= state->owner->so_cred,
    	};
    	struct nfs4_lock_state *lsp;
    	int status;
    
    
    	arg.lock_owner.clientid = clp->cl_clientid;
    
    	status = nfs4_set_lock_state(state, request);
    	if (status != 0)
    		goto out;
    	lsp = request->fl_u.nfs4_fl.owner;
    
    	arg.lock_owner.id = lsp->ls_id.id;
    
    	arg.lock_owner.s_dev = server->s_dev;
    
    	status = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
    
    	switch (status) {
    		case 0:
    			request->fl_type = F_UNLCK;
    			break;
    		case -NFS4ERR_DENIED:
    			status = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	request->fl_ops->fl_release_private(request);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return status;
    }
    
    static int nfs4_proc_getlk(struct nfs4_state *state, int cmd, struct file_lock *request)
    {
    	struct nfs4_exception exception = { };
    	int err;
    
    	do {
    		err = nfs4_handle_exception(NFS_SERVER(state->inode),
    				_nfs4_proc_getlk(state, cmd, request),
    				&exception);
    	} while (exception.retry);
    	return err;
    }