diff options
author | Pavel Emelyanov <xemul@openvz.org> | 2013-10-10 09:10:46 -0400 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2014-04-02 09:38:48 -0400 |
commit | 8373200b124d03de7fa2e99be56de8642e604e9e (patch) | |
tree | 853b6590ced17b0449883093350bf681e5e9cbd7 | |
parent | d5cd66c58edf10a7ee786659994595fd43995aab (diff) |
fuse: Trust kernel i_size only
Make fuse think that when writeback is on the inode's i_size is always
up-to-date and not update it with the value received from the userspace.
This is done because the page cache code may update i_size without letting
the FS know.
This assumption implies fixing the previously introduced short-read helper --
when a short read occurs the 'hole' is filled with zeroes.
fuse_file_fallocate() is also fixed because now we should keep i_size up to
date, so it must be updated if FUSE_FALLOCATE request succeeded.
Signed-off-by: Maxim V. Patlasov <MPatlasov@parallels.com>
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
-rw-r--r-- | fs/fuse/dir.c | 13 | ||||
-rw-r--r-- | fs/fuse/file.c | 21 | ||||
-rw-r--r-- | fs/fuse/inode.c | 11 |
3 files changed, 39 insertions, 6 deletions
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 1d1292c581c3..c52f143da9ad 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c | |||
@@ -839,6 +839,11 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, | |||
839 | struct kstat *stat) | 839 | struct kstat *stat) |
840 | { | 840 | { |
841 | unsigned int blkbits; | 841 | unsigned int blkbits; |
842 | struct fuse_conn *fc = get_fuse_conn(inode); | ||
843 | |||
844 | /* see the comment in fuse_change_attributes() */ | ||
845 | if (fc->writeback_cache && S_ISREG(inode->i_mode)) | ||
846 | attr->size = i_size_read(inode); | ||
842 | 847 | ||
843 | stat->dev = inode->i_sb->s_dev; | 848 | stat->dev = inode->i_sb->s_dev; |
844 | stat->ino = attr->ino; | 849 | stat->ino = attr->ino; |
@@ -1580,6 +1585,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr, | |||
1580 | struct fuse_setattr_in inarg; | 1585 | struct fuse_setattr_in inarg; |
1581 | struct fuse_attr_out outarg; | 1586 | struct fuse_attr_out outarg; |
1582 | bool is_truncate = false; | 1587 | bool is_truncate = false; |
1588 | bool is_wb = fc->writeback_cache; | ||
1583 | loff_t oldsize; | 1589 | loff_t oldsize; |
1584 | int err; | 1590 | int err; |
1585 | 1591 | ||
@@ -1651,7 +1657,9 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr, | |||
1651 | fuse_change_attributes_common(inode, &outarg.attr, | 1657 | fuse_change_attributes_common(inode, &outarg.attr, |
1652 | attr_timeout(&outarg)); | 1658 | attr_timeout(&outarg)); |
1653 | oldsize = inode->i_size; | 1659 | oldsize = inode->i_size; |
1654 | i_size_write(inode, outarg.attr.size); | 1660 | /* see the comment in fuse_change_attributes() */ |
1661 | if (!is_wb || is_truncate || !S_ISREG(inode->i_mode)) | ||
1662 | i_size_write(inode, outarg.attr.size); | ||
1655 | 1663 | ||
1656 | if (is_truncate) { | 1664 | if (is_truncate) { |
1657 | /* NOTE: this may release/reacquire fc->lock */ | 1665 | /* NOTE: this may release/reacquire fc->lock */ |
@@ -1663,7 +1671,8 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr, | |||
1663 | * Only call invalidate_inode_pages2() after removing | 1671 | * Only call invalidate_inode_pages2() after removing |
1664 | * FUSE_NOWRITE, otherwise fuse_launder_page() would deadlock. | 1672 | * FUSE_NOWRITE, otherwise fuse_launder_page() would deadlock. |
1665 | */ | 1673 | */ |
1666 | if (S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { | 1674 | if ((is_truncate || !is_wb) && |
1675 | S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { | ||
1667 | truncate_pagecache(inode, outarg.attr.size); | 1676 | truncate_pagecache(inode, outarg.attr.size); |
1668 | invalidate_inode_pages2(inode->i_mapping); | 1677 | invalidate_inode_pages2(inode->i_mapping); |
1669 | } | 1678 | } |
diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 592d7b48e421..c091a17d3ffc 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c | |||
@@ -675,9 +675,26 @@ static void fuse_short_read(struct fuse_req *req, struct inode *inode, | |||
675 | u64 attr_ver) | 675 | u64 attr_ver) |
676 | { | 676 | { |
677 | size_t num_read = req->out.args[0].size; | 677 | size_t num_read = req->out.args[0].size; |
678 | struct fuse_conn *fc = get_fuse_conn(inode); | ||
679 | |||
680 | if (fc->writeback_cache) { | ||
681 | /* | ||
682 | * A hole in a file. Some data after the hole are in page cache, | ||
683 | * but have not reached the client fs yet. So, the hole is not | ||
684 | * present there. | ||
685 | */ | ||
686 | int i; | ||
687 | int start_idx = num_read >> PAGE_CACHE_SHIFT; | ||
688 | size_t off = num_read & (PAGE_CACHE_SIZE - 1); | ||
678 | 689 | ||
679 | loff_t pos = page_offset(req->pages[0]) + num_read; | 690 | for (i = start_idx; i < req->num_pages; i++) { |
680 | fuse_read_update_size(inode, pos, attr_ver); | 691 | zero_user_segment(req->pages[i], off, PAGE_CACHE_SIZE); |
692 | off = 0; | ||
693 | } | ||
694 | } else { | ||
695 | loff_t pos = page_offset(req->pages[0]) + num_read; | ||
696 | fuse_read_update_size(inode, pos, attr_ver); | ||
697 | } | ||
681 | } | 698 | } |
682 | 699 | ||
683 | static int fuse_readpage(struct file *file, struct page *page) | 700 | static int fuse_readpage(struct file *file, struct page *page) |
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index d468643a68b2..c668c8436894 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c | |||
@@ -197,6 +197,7 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr, | |||
197 | { | 197 | { |
198 | struct fuse_conn *fc = get_fuse_conn(inode); | 198 | struct fuse_conn *fc = get_fuse_conn(inode); |
199 | struct fuse_inode *fi = get_fuse_inode(inode); | 199 | struct fuse_inode *fi = get_fuse_inode(inode); |
200 | bool is_wb = fc->writeback_cache; | ||
200 | loff_t oldsize; | 201 | loff_t oldsize; |
201 | struct timespec old_mtime; | 202 | struct timespec old_mtime; |
202 | 203 | ||
@@ -211,10 +212,16 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr, | |||
211 | fuse_change_attributes_common(inode, attr, attr_valid); | 212 | fuse_change_attributes_common(inode, attr, attr_valid); |
212 | 213 | ||
213 | oldsize = inode->i_size; | 214 | oldsize = inode->i_size; |
214 | i_size_write(inode, attr->size); | 215 | /* |
216 | * In case of writeback_cache enabled, the cached writes beyond EOF | ||
217 | * extend local i_size without keeping userspace server in sync. So, | ||
218 | * attr->size coming from server can be stale. We cannot trust it. | ||
219 | */ | ||
220 | if (!is_wb || !S_ISREG(inode->i_mode)) | ||
221 | i_size_write(inode, attr->size); | ||
215 | spin_unlock(&fc->lock); | 222 | spin_unlock(&fc->lock); |
216 | 223 | ||
217 | if (S_ISREG(inode->i_mode)) { | 224 | if (!is_wb && S_ISREG(inode->i_mode)) { |
218 | bool inval = false; | 225 | bool inval = false; |
219 | 226 | ||
220 | if (oldsize != attr->size) { | 227 | if (oldsize != attr->size) { |