Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next
[sfrench/cifs-2.6.git] / fs / nfs / nfs42proc.c
index 5f59b6f65a421911fb35d196636dd7d0a7488053..ac5b784a1de05c864958f1a16646fac034b402d5 100644 (file)
@@ -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, &copy->stateid,
+                               NFS4_STATEID_SIZE))
+                       continue;
+               found_pending = true;
+               list_del(&copy->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(&copy->stateid, &res->write_res.stateid, NFS4_STATEID_SIZE);
+       init_completion(&copy->completion);
+       copy->parent_state = ctx->state;
+
+       list_add_tail(&copy->copies, &server->ss_copies);
+       spin_unlock(&server->nfs_client->cl_lock);
+
+       status = wait_for_completion_interruptible(&copy->completion);
+       spin_lock(&server->nfs_client->cl_lock);
+       list_del_init(&copy->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, &copy->verf, sizeof(copy->verf));
+       status = -copy->error;
+
+       kfree(copy);
+       return status;
+out_cancel:
+       nfs42_do_offload_cancel_async(dst, &copy->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)
 {