Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/livepatchin...
[linux-2.6-microblaze.git] / fs / nfs / nfs4file.c
index 339663d..620de90 100644 (file)
@@ -133,14 +133,55 @@ static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
                                      struct file *file_out, loff_t pos_out,
                                      size_t count, unsigned int flags)
 {
+       struct nfs42_copy_notify_res *cn_resp = NULL;
+       struct nl4_server *nss = NULL;
+       nfs4_stateid *cnrs = NULL;
+       ssize_t ret;
+       bool sync = false;
+
        /* Only offload copy if superblock is the same */
-       if (file_inode(file_in)->i_sb != file_inode(file_out)->i_sb)
+       if (file_in->f_op != &nfs4_file_operations)
                return -EXDEV;
        if (!nfs_server_capable(file_inode(file_out), NFS_CAP_COPY))
                return -EOPNOTSUPP;
        if (file_inode(file_in) == file_inode(file_out))
                return -EOPNOTSUPP;
-       return nfs42_proc_copy(file_in, pos_in, file_out, pos_out, count);
+       /* if the copy size if smaller than 2 RPC payloads, make it
+        * synchronous
+        */
+       if (count <= 2 * NFS_SERVER(file_inode(file_in))->rsize)
+               sync = true;
+retry:
+       if (!nfs42_files_from_same_server(file_in, file_out)) {
+               /* for inter copy, if copy size if smaller than 12 RPC
+                * payloads, fallback to traditional copy. There are
+                * 14 RPCs during an NFSv4.x mount between source/dest
+                * servers.
+                */
+               if (sync ||
+                       count <= 14 * NFS_SERVER(file_inode(file_in))->rsize)
+                       return -EOPNOTSUPP;
+               cn_resp = kzalloc(sizeof(struct nfs42_copy_notify_res),
+                               GFP_NOFS);
+               if (unlikely(cn_resp == NULL))
+                       return -ENOMEM;
+
+               ret = nfs42_proc_copy_notify(file_in, file_out, cn_resp);
+               if (ret) {
+                       ret = -EOPNOTSUPP;
+                       goto out;
+               }
+               nss = &cn_resp->cnr_src;
+               cnrs = &cn_resp->cnr_stateid;
+       }
+       ret = nfs42_proc_copy(file_in, pos_in, file_out, pos_out, count,
+                               nss, cnrs, sync);
+out:
+       if (!nfs42_files_from_same_server(file_in, file_out))
+               kfree(cn_resp);
+       if (ret == -EAGAIN)
+               goto retry;
+       return ret;
 }
 
 static ssize_t nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
@@ -263,6 +304,102 @@ out_unlock:
 out:
        return ret < 0 ? ret : count;
 }
+
+static int read_name_gen = 1;
+#define SSC_READ_NAME_BODY "ssc_read_%d"
+
+struct file *
+nfs42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh,
+               nfs4_stateid *stateid)
+{
+       struct nfs_fattr fattr;
+       struct file *filep, *res;
+       struct nfs_server *server;
+       struct inode *r_ino = NULL;
+       struct nfs_open_context *ctx;
+       struct nfs4_state_owner *sp;
+       char *read_name = NULL;
+       int len, status = 0;
+
+       server = NFS_SERVER(ss_mnt->mnt_root->d_inode);
+
+       nfs_fattr_init(&fattr);
+
+       status = nfs4_proc_getattr(server, src_fh, &fattr, NULL, NULL);
+       if (status < 0) {
+               res = ERR_PTR(status);
+               goto out;
+       }
+
+       res = ERR_PTR(-ENOMEM);
+       len = strlen(SSC_READ_NAME_BODY) + 16;
+       read_name = kzalloc(len, GFP_NOFS);
+       if (read_name == NULL)
+               goto out;
+       snprintf(read_name, len, SSC_READ_NAME_BODY, read_name_gen++);
+
+       r_ino = nfs_fhget(ss_mnt->mnt_root->d_inode->i_sb, src_fh, &fattr,
+                       NULL);
+       if (IS_ERR(r_ino)) {
+               res = ERR_CAST(r_ino);
+               goto out_free_name;
+       }
+
+       filep = alloc_file_pseudo(r_ino, ss_mnt, read_name, FMODE_READ,
+                                    r_ino->i_fop);
+       if (IS_ERR(filep)) {
+               res = ERR_CAST(filep);
+               goto out_free_name;
+       }
+       filep->f_mode |= FMODE_READ;
+
+       ctx = alloc_nfs_open_context(filep->f_path.dentry, filep->f_mode,
+                                       filep);
+       if (IS_ERR(ctx)) {
+               res = ERR_CAST(ctx);
+               goto out_filep;
+       }
+
+       res = ERR_PTR(-EINVAL);
+       sp = nfs4_get_state_owner(server, ctx->cred, GFP_KERNEL);
+       if (sp == NULL)
+               goto out_ctx;
+
+       ctx->state = nfs4_get_open_state(r_ino, sp);
+       if (ctx->state == NULL)
+               goto out_stateowner;
+
+       set_bit(NFS_SRV_SSC_COPY_STATE, &ctx->state->flags);
+       set_bit(NFS_OPEN_STATE, &ctx->state->flags);
+       memcpy(&ctx->state->open_stateid.other, &stateid->other,
+              NFS4_STATEID_OTHER_SIZE);
+       update_open_stateid(ctx->state, stateid, NULL, filep->f_mode);
+
+       nfs_file_set_open_context(filep, ctx);
+       put_nfs_open_context(ctx);
+
+       file_ra_state_init(&filep->f_ra, filep->f_mapping->host->i_mapping);
+       res = filep;
+out_free_name:
+       kfree(read_name);
+out:
+       return res;
+out_stateowner:
+       nfs4_put_state_owner(sp);
+out_ctx:
+       put_nfs_open_context(ctx);
+out_filep:
+       fput(filep);
+       goto out_free_name;
+}
+EXPORT_SYMBOL_GPL(nfs42_ssc_open);
+void nfs42_ssc_close(struct file *filep)
+{
+       struct nfs_open_context *ctx = nfs_file_open_context(filep);
+
+       ctx->state->flags = 0;
+}
+EXPORT_SYMBOL_GPL(nfs42_ssc_close);
 #endif /* CONFIG_NFS_V4_2 */
 
 const struct file_operations nfs4_file_operations = {