NFSv4.2: Fix writeback races in nfs4_copy_file_range
authorTrond Myklebust <trond.myklebust@primarydata.com>
Sat, 25 Jun 2016 22:12:03 +0000 (18:12 -0400)
committerTrond Myklebust <trond.myklebust@primarydata.com>
Tue, 5 Jul 2016 23:11:07 +0000 (19:11 -0400)
We need to ensure that any writes to the destination file are serialised
with the copy, meaning that the writeback has to occur under the inode lock.

Also relax the writeback requirement on the source, and rely on the
stateid checking to tell us if the source rebooted. Add the helper
nfs_filemap_write_and_wait_range() to call pnfs_sync_inode() as
is appropriate for pNFS servers that may need a layoutcommit.

Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
fs/nfs/internal.h
fs/nfs/nfs42proc.c
fs/nfs/nfs4file.c
fs/nfs/write.c

index 01dccf18da0a0a8c4d4f3a5bfdf93c7d7d77564d..3b01c9146e156512056b7036aca598f2f99e9869 100644 (file)
@@ -512,6 +512,9 @@ int nfs_key_timeout_notify(struct file *filp, struct inode *inode);
 bool nfs_ctx_key_to_expire(struct nfs_open_context *ctx);
 void nfs_pageio_stop_mirroring(struct nfs_pageio_descriptor *pgio);
 
+int nfs_filemap_write_and_wait_range(struct address_space *mapping,
+               loff_t lstart, loff_t lend);
+
 #ifdef CONFIG_NFS_V4_1
 static inline
 void nfs_clear_pnfs_ds_commit_verifiers(struct pnfs_ds_commit_info *cinfo)
index 0f9f536e647b7242fd145643b3a592aec048dbf7..b7d457cea03f01b17b09a2ebb1bdbcdb1f5d2212 100644 (file)
@@ -156,11 +156,20 @@ static ssize_t _nfs42_proc_copy(struct file *src, loff_t pos_src,
        if (status)
                return status;
 
+       status = nfs_filemap_write_and_wait_range(file_inode(src)->i_mapping,
+                       pos_src, pos_src + (loff_t)count - 1);
+       if (status)
+               return status;
+
        status = nfs4_set_rw_stateid(&args.dst_stateid, dst_lock->open_context,
                                     dst_lock, FMODE_WRITE);
        if (status)
                return status;
 
+       status = nfs_sync_inode(dst_inode);
+       if (status)
+               return status;
+
        status = nfs4_call_sync(server->client, server, &msg,
                                &args.seq_args, &res.seq_res, 0);
        if (status == -ENOTSUPP)
index 014b0e41ace5a8f5b17510fd32d9403faa0c4084..7cdc0ab9e6f5801b2807ea38be8694fafe8f6697 100644 (file)
@@ -133,21 +133,9 @@ 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 inode *in_inode = file_inode(file_in);
-       struct inode *out_inode = file_inode(file_out);
-       int ret;
-
-       if (in_inode == out_inode)
+       if (file_inode(file_in) == file_inode(file_out))
                return -EINVAL;
 
-       /* flush any pending writes */
-       ret = nfs_sync_inode(in_inode);
-       if (ret)
-               return ret;
-       ret = nfs_sync_inode(out_inode);
-       if (ret)
-               return ret;
-
        return nfs42_proc_copy(file_in, pos_in, file_out, pos_out, count);
 }
 
index 3087fb6f1983015e044e602f1ea6bd35d837f50a..538a473b324b110a11b50f87a7fc04b991d04dd8 100644 (file)
@@ -1912,6 +1912,24 @@ out_mark_dirty:
 }
 EXPORT_SYMBOL_GPL(nfs_write_inode);
 
+/*
+ * Wrapper for filemap_write_and_wait_range()
+ *
+ * Needed for pNFS in order to ensure data becomes visible to the
+ * client.
+ */
+int nfs_filemap_write_and_wait_range(struct address_space *mapping,
+               loff_t lstart, loff_t lend)
+{
+       int ret;
+
+       ret = filemap_write_and_wait_range(mapping, lstart, lend);
+       if (ret == 0)
+               ret = pnfs_sync_inode(mapping->host, true);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(nfs_filemap_write_and_wait_range);
+
 /*
  * flush the inode to disk.
  */