aboutsummaryrefslogtreecommitdiffstats
path: root/fs/cifs/cifsfs.c
diff options
context:
space:
mode:
authorJeff Layton <jlayton@redhat.com>2007-11-20 18:19:03 -0500
committerSteve French <sfrench@us.ibm.com>2007-11-20 18:19:03 -0500
commitcea218054ad277d6c126890213afde07b4eb1602 (patch)
tree8bfbd3c7d8ab94d35ec749ed4e0d66b1f6b69101 /fs/cifs/cifsfs.c
parent2a97468024fb5b6eccee2a67a7796485c829343a (diff)
[CIFS] Fix potential data corruption when writing out cached dirty pages
Fix RedHat bug 329431 The idea here is separate "conscious" from "unconscious" flushes. Conscious flushes are those due to a fsync() or close(). Unconscious ones are flushes that occur as a side effect of some other operation or due to memory pressure. Currently, when an error occurs during an unconscious flush (ENOSPC or EIO), we toss out the page and don't preserve that error to report to the user when a conscious flush occurs. If after the unconscious flush, there are no more dirty pages for the inode, the conscious flush will simply return success even though there were previous errors when writing out pages. This can lead to data corruption. The easiest way to reproduce this is to mount up a CIFS share that's very close to being full or where the user is very close to quota. mv a file to the share that's slightly larger than the quota allows. The writes will all succeed (since they go to pagecache). The mv will do a setattr to set the new file's attributes. This calls filemap_write_and_wait, which will return an error since all of the pages can't be written out. Then later, when the flush and release ops occur, there are no more dirty pages in pagecache for the file and those operations return 0. mv then assumes that the file was written out correctly and deletes the original. CIFS already has a write_behind_rc variable where it stores the results from earlier flushes, but that value is only reported in cifs_close. Since the VFS ignores the return value from the release operation, this isn't helpful. We should be reporting this error during the flush operation. This patch does the following: 1) changes cifs_fsync to use filemap_write_and_wait and cifs_flush and also sync to check its return code. If it returns successful, they then check the value of write_behind_rc to see if an earlier flush had reported any errors. If so, they return that error and clear write_behind_rc. 2) sets write_behind_rc in a few other places where pages are written out as a side effect of other operations and the code waits on them. 3) changes cifs_setattr to only call filemap_write_and_wait for ATTR_SIZE changes. 4) makes cifs_writepages accurately distinguish between EIO and ENOSPC errors when writing out pages. Some simple testing indicates that the patch works as expected and that it fixes the reproduceable known problem. Acked-by: Dave Kleikamp <shaggy@austin.rr.com> Signed-off-by: Jeff Layton <jlayton@redhat.com> Signed-off-by: Steve French <sfrench@us.ibm.com>
Diffstat (limited to 'fs/cifs/cifsfs.c')
-rw-r--r--fs/cifs/cifsfs.c7
1 files changed, 5 insertions, 2 deletions
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index 416dc9fe8961..093beaa3900d 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -266,6 +266,7 @@ cifs_alloc_inode(struct super_block *sb)
266 cifs_inode->cifsAttrs = 0x20; /* default */ 266 cifs_inode->cifsAttrs = 0x20; /* default */
267 atomic_set(&cifs_inode->inUse, 0); 267 atomic_set(&cifs_inode->inUse, 0);
268 cifs_inode->time = 0; 268 cifs_inode->time = 0;
269 cifs_inode->write_behind_rc = 0;
269 /* Until the file is open and we have gotten oplock 270 /* Until the file is open and we have gotten oplock
270 info back from the server, can not assume caching of 271 info back from the server, can not assume caching of
271 file data or metadata */ 272 file data or metadata */
@@ -852,7 +853,7 @@ static int cifs_oplock_thread(void *dummyarg)
852 struct cifsTconInfo *pTcon; 853 struct cifsTconInfo *pTcon;
853 struct inode *inode; 854 struct inode *inode;
854 __u16 netfid; 855 __u16 netfid;
855 int rc; 856 int rc, waitrc = 0;
856 857
857 set_freezable(); 858 set_freezable();
858 do { 859 do {
@@ -884,9 +885,11 @@ static int cifs_oplock_thread(void *dummyarg)
884 filemap_fdatawrite(inode->i_mapping); 885 filemap_fdatawrite(inode->i_mapping);
885 if (CIFS_I(inode)->clientCanCacheRead 886 if (CIFS_I(inode)->clientCanCacheRead
886 == 0) { 887 == 0) {
887 filemap_fdatawait(inode->i_mapping); 888 waitrc = filemap_fdatawait(inode->i_mapping);
888 invalidate_remote_inode(inode); 889 invalidate_remote_inode(inode);
889 } 890 }
891 if (rc == 0)
892 rc = waitrc;
890 } else 893 } else
891 rc = 0; 894 rc = 0;
892 /* mutex_unlock(&inode->i_mutex);*/ 895 /* mutex_unlock(&inode->i_mutex);*/