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) { |
