diff options
Diffstat (limited to 'fs/nfs/nfs42proc.c')
| -rw-r--r-- | fs/nfs/nfs42proc.c | 209 | 
1 files changed, 203 insertions, 6 deletions
diff --git a/fs/nfs/nfs42proc.c b/fs/nfs/nfs42proc.c index 5f59b6f65a42..ac5b784a1de0 100644 --- a/fs/nfs/nfs42proc.c +++ b/fs/nfs/nfs42proc.c @@ -17,6 +17,7 @@  #include "internal.h"  #define NFSDBG_FACILITY NFSDBG_PROC +static int nfs42_do_offload_cancel_async(struct file *dst, nfs4_stateid *std);  static int _nfs42_proc_fallocate(struct rpc_message *msg, struct file *filep,  		struct nfs_lock_context *lock, loff_t offset, loff_t len) @@ -130,6 +131,91 @@ out_unlock:  	return err;  } +static int handle_async_copy(struct nfs42_copy_res *res, +			     struct nfs_server *server, +			     struct file *src, +			     struct file *dst, +			     nfs4_stateid *src_stateid) +{ +	struct nfs4_copy_state *copy; +	int status = NFS4_OK; +	bool found_pending = false; +	struct nfs_open_context *ctx = nfs_file_open_context(dst); + +	spin_lock(&server->nfs_client->cl_lock); +	list_for_each_entry(copy, &server->nfs_client->pending_cb_stateids, +				copies) { +		if (memcmp(&res->write_res.stateid, ©->stateid, +				NFS4_STATEID_SIZE)) +			continue; +		found_pending = true; +		list_del(©->copies); +		break; +	} +	if (found_pending) { +		spin_unlock(&server->nfs_client->cl_lock); +		goto out; +	} + +	copy = kzalloc(sizeof(struct nfs4_copy_state), GFP_NOFS); +	if (!copy) { +		spin_unlock(&server->nfs_client->cl_lock); +		return -ENOMEM; +	} +	memcpy(©->stateid, &res->write_res.stateid, NFS4_STATEID_SIZE); +	init_completion(©->completion); +	copy->parent_state = ctx->state; + +	list_add_tail(©->copies, &server->ss_copies); +	spin_unlock(&server->nfs_client->cl_lock); + +	status = wait_for_completion_interruptible(©->completion); +	spin_lock(&server->nfs_client->cl_lock); +	list_del_init(©->copies); +	spin_unlock(&server->nfs_client->cl_lock); +	if (status == -ERESTARTSYS) { +		goto out_cancel; +	} else if (copy->flags) { +		status = -EAGAIN; +		goto out_cancel; +	} +out: +	res->write_res.count = copy->count; +	memcpy(&res->write_res.verifier, ©->verf, sizeof(copy->verf)); +	status = -copy->error; + +	kfree(copy); +	return status; +out_cancel: +	nfs42_do_offload_cancel_async(dst, ©->stateid); +	kfree(copy); +	return status; +} + +static int process_copy_commit(struct file *dst, loff_t pos_dst, +			       struct nfs42_copy_res *res) +{ +	struct nfs_commitres cres; +	int status = -ENOMEM; + +	cres.verf = kzalloc(sizeof(struct nfs_writeverf), GFP_NOFS); +	if (!cres.verf) +		goto out; + +	status = nfs4_proc_commit(dst, pos_dst, res->write_res.count, &cres); +	if (status) +		goto out_free; +	if (nfs_write_verifier_cmp(&res->write_res.verifier.verifier, +				    &cres.verf->verifier)) { +		dprintk("commit verf differs from copy verf\n"); +		status = -EAGAIN; +	} +out_free: +	kfree(cres.verf); +out: +	return status; +} +  static ssize_t _nfs42_proc_copy(struct file *src,  				struct nfs_lock_context *src_lock,  				struct file *dst, @@ -168,9 +254,16 @@ static ssize_t _nfs42_proc_copy(struct file *src,  	if (status)  		return status; -	res->commit_res.verf = kzalloc(sizeof(struct nfs_writeverf), GFP_NOFS); -	if (!res->commit_res.verf) -		return -ENOMEM; +	res->commit_res.verf = NULL; +	if (args->sync) { +		res->commit_res.verf = +			kzalloc(sizeof(struct nfs_writeverf), GFP_NOFS); +		if (!res->commit_res.verf) +			return -ENOMEM; +	} +	set_bit(NFS_CLNT_DST_SSC_COPY_STATE, +		&dst_lock->open_context->state->flags); +  	status = nfs4_call_sync(server->client, server, &msg,  				&args->seq_args, &res->seq_res, 0);  	if (status == -ENOTSUPP) @@ -178,18 +271,34 @@ static ssize_t _nfs42_proc_copy(struct file *src,  	if (status)  		goto out; -	if (nfs_write_verifier_cmp(&res->write_res.verifier.verifier, +	if (args->sync && +		nfs_write_verifier_cmp(&res->write_res.verifier.verifier,  				    &res->commit_res.verf->verifier)) {  		status = -EAGAIN;  		goto out;  	} +	if (!res->synchronous) { +		status = handle_async_copy(res, server, src, dst, +				&args->src_stateid); +		if (status) +			return status; +	} + +	if ((!res->synchronous || !args->sync) && +			res->write_res.verifier.committed != NFS_FILE_SYNC) { +		status = process_copy_commit(dst, pos_dst, res); +		if (status) +			return status; +	} +  	truncate_pagecache_range(dst_inode, pos_dst,  				 pos_dst + res->write_res.count);  	status = res->write_res.count;  out: -	kfree(res->commit_res.verf); +	if (args->sync) +		kfree(res->commit_res.verf);  	return status;  } @@ -206,6 +315,7 @@ ssize_t nfs42_proc_copy(struct file *src, loff_t pos_src,  		.dst_fh		= NFS_FH(file_inode(dst)),  		.dst_pos	= pos_dst,  		.count		= count, +		.sync		= false,  	};  	struct nfs42_copy_res res;  	struct nfs4_exception src_exception = { @@ -247,7 +357,11 @@ ssize_t nfs42_proc_copy(struct file *src, loff_t pos_src,  		if (err == -ENOTSUPP) {  			err = -EOPNOTSUPP;  			break; -		} if (err == -EAGAIN) { +		} else if (err == -EAGAIN) { +			dst_exception.retry = 1; +			continue; +		} else if (err == -NFS4ERR_OFFLOAD_NO_REQS && !args.sync) { +			args.sync = true;  			dst_exception.retry = 1;  			continue;  		} @@ -264,6 +378,89 @@ out_put_src_lock:  	return err;  } +struct nfs42_offloadcancel_data { +	struct nfs_server *seq_server; +	struct nfs42_offload_status_args args; +	struct nfs42_offload_status_res res; +}; + +static void nfs42_offload_cancel_prepare(struct rpc_task *task, void *calldata) +{ +	struct nfs42_offloadcancel_data *data = calldata; + +	nfs4_setup_sequence(data->seq_server->nfs_client, +				&data->args.osa_seq_args, +				&data->res.osr_seq_res, task); +} + +static void nfs42_offload_cancel_done(struct rpc_task *task, void *calldata) +{ +	struct nfs42_offloadcancel_data *data = calldata; + +	nfs41_sequence_done(task, &data->res.osr_seq_res); +	if (task->tk_status && +		nfs4_async_handle_error(task, data->seq_server, NULL, +			NULL) == -EAGAIN) +		rpc_restart_call_prepare(task); +} + +static void nfs42_free_offloadcancel_data(void *data) +{ +	kfree(data); +} + +static const struct rpc_call_ops nfs42_offload_cancel_ops = { +	.rpc_call_prepare = nfs42_offload_cancel_prepare, +	.rpc_call_done = nfs42_offload_cancel_done, +	.rpc_release = nfs42_free_offloadcancel_data, +}; + +static int nfs42_do_offload_cancel_async(struct file *dst, +					 nfs4_stateid *stateid) +{ +	struct nfs_server *dst_server = NFS_SERVER(file_inode(dst)); +	struct nfs42_offloadcancel_data *data = NULL; +	struct nfs_open_context *ctx = nfs_file_open_context(dst); +	struct rpc_task *task; +	struct rpc_message msg = { +		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OFFLOAD_CANCEL], +		.rpc_cred = ctx->cred, +	}; +	struct rpc_task_setup task_setup_data = { +		.rpc_client = dst_server->client, +		.rpc_message = &msg, +		.callback_ops = &nfs42_offload_cancel_ops, +		.workqueue = nfsiod_workqueue, +		.flags = RPC_TASK_ASYNC, +	}; +	int status; + +	if (!(dst_server->caps & NFS_CAP_OFFLOAD_CANCEL)) +		return -EOPNOTSUPP; + +	data = kzalloc(sizeof(struct nfs42_offloadcancel_data), GFP_NOFS); +	if (data == NULL) +		return -ENOMEM; + +	data->seq_server = dst_server; +	data->args.osa_src_fh = NFS_FH(file_inode(dst)); +	memcpy(&data->args.osa_stateid, stateid, +		sizeof(data->args.osa_stateid)); +	msg.rpc_argp = &data->args; +	msg.rpc_resp = &data->res; +	task_setup_data.callback_data = data; +	nfs4_init_sequence(&data->args.osa_seq_args, &data->res.osr_seq_res, +			   1, 0); +	task = rpc_run_task(&task_setup_data); +	if (IS_ERR(task)) +		return PTR_ERR(task); +	status = rpc_wait_for_completion_task(task); +	if (status == -ENOTSUPP) +		dst_server->caps &= ~NFS_CAP_OFFLOAD_CANCEL; +	rpc_put_task(task); +	return status; +} +  static loff_t _nfs42_proc_llseek(struct file *filep,  		struct nfs_lock_context *lock, loff_t offset, int whence)  {  |