NFS: Fix fdatasync/fsync() when confronted with a server reboot
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Tue, 11 Sep 2012 20:01:22 +0000 (16:01 -0400)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Fri, 28 Sep 2012 20:03:05 +0000 (16:03 -0400)
If the server reboots before it can commit the unstable writes to disk,
then nfs_commit_release_pages() will detect this when it compares the
verifier returned by COMMIT to the one returned by WRITE. When this
happens, the client needs to resend those writes in order to guarantee
that they make it to stable storage.

This patch adds a signalling mechanism to notify fsync() that it
needs to retry all writes before it can exit.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/file.c
fs/nfs/nfs4file.c
fs/nfs/write.c
include/linux/nfs_fs.h

index 6a7fcab7ecb3115c7630573c17f4d8285a418591..cc9b56691bef42a5205011192542f92c906c616f 100644 (file)
@@ -259,7 +259,7 @@ nfs_file_fsync_commit(struct file *file, loff_t start, loff_t end, int datasync)
        struct dentry *dentry = file->f_path.dentry;
        struct nfs_open_context *ctx = nfs_file_open_context(file);
        struct inode *inode = dentry->d_inode;
-       int have_error, status;
+       int have_error, do_resend, status;
        int ret = 0;
 
        dprintk("NFS: fsync file(%s/%s) datasync %d\n",
@@ -267,15 +267,23 @@ nfs_file_fsync_commit(struct file *file, loff_t start, loff_t end, int datasync)
                        datasync);
 
        nfs_inc_stats(inode, NFSIOS_VFSFSYNC);
+       do_resend = test_and_clear_bit(NFS_CONTEXT_RESEND_WRITES, &ctx->flags);
        have_error = test_and_clear_bit(NFS_CONTEXT_ERROR_WRITE, &ctx->flags);
        status = nfs_commit_inode(inode, FLUSH_SYNC);
-       if (status >= 0 && ret < 0)
-               status = ret;
        have_error |= test_bit(NFS_CONTEXT_ERROR_WRITE, &ctx->flags);
-       if (have_error)
+       if (have_error) {
                ret = xchg(&ctx->error, 0);
-       if (!ret && status < 0)
+               if (ret)
+                       goto out;
+       }
+       if (status < 0) {
                ret = status;
+               goto out;
+       }
+       do_resend |= test_bit(NFS_CONTEXT_RESEND_WRITES, &ctx->flags);
+       if (do_resend)
+               ret = -EAGAIN;
+out:
        return ret;
 }
 EXPORT_SYMBOL_GPL(nfs_file_fsync_commit);
@@ -286,13 +294,15 @@ nfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
        int ret;
        struct inode *inode = file->f_path.dentry->d_inode;
 
-       ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
-       if (ret != 0)
-               goto out;
-       mutex_lock(&inode->i_mutex);
-       ret = nfs_file_fsync_commit(file, start, end, datasync);
-       mutex_unlock(&inode->i_mutex);
-out:
+       do {
+               ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
+               if (ret != 0)
+                       break;
+               mutex_lock(&inode->i_mutex);
+               ret = nfs_file_fsync_commit(file, start, end, datasync);
+               mutex_unlock(&inode->i_mutex);
+       } while (ret == -EAGAIN);
+
        return ret;
 }
 
index eb5eb8eef4d34db3c7bafe3c84c1db0bf43e8974..eef1b38a1b082b9530911069fa902e53bd181e9f 100644 (file)
@@ -95,16 +95,18 @@ nfs4_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
        int ret;
        struct inode *inode = file->f_path.dentry->d_inode;
 
-       ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
-       if (ret != 0)
-               goto out;
-       mutex_lock(&inode->i_mutex);
-       ret = nfs_file_fsync_commit(file, start, end, datasync);
-       if (!ret && !datasync)
-               /* application has asked for meta-data sync */
-               ret = pnfs_layoutcommit_inode(inode, true);
-       mutex_unlock(&inode->i_mutex);
-out:
+       do {
+               ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
+               if (ret != 0)
+                       break;
+               mutex_lock(&inode->i_mutex);
+               ret = nfs_file_fsync_commit(file, start, end, datasync);
+               if (!ret && !datasync)
+                       /* application has asked for meta-data sync */
+                       ret = pnfs_layoutcommit_inode(inode, true);
+               mutex_unlock(&inode->i_mutex);
+       } while (ret == -EAGAIN);
+
        return ret;
 }
 
index e1b5fe4d873abb50a5fb64febef2584d10d4b149..9347ab7c9574fa3b2468a54449d85d366aaf6b61 100644 (file)
@@ -1580,6 +1580,7 @@ static void nfs_commit_release_pages(struct nfs_commit_data *data)
                /* We have a mismatch. Write the page again */
                dprintk(" mismatch\n");
                nfs_mark_request_dirty(req);
+               set_bit(NFS_CONTEXT_RESEND_WRITES, &req->wb_context->flags);
        next:
                nfs_unlock_and_release_request(req);
        }
index 869eac0c263511d058d50b1aa1a5b3ff996528a9..383f3313f0531441dfd883c8fbce74c7e2f66a57 100644 (file)
@@ -103,6 +103,7 @@ struct nfs_open_context {
 
        unsigned long flags;
 #define NFS_CONTEXT_ERROR_WRITE                (0)
+#define NFS_CONTEXT_RESEND_WRITES      (1)
        int error;
 
        struct list_head list;