From bcfa416e38d180c5687219f05c23f343879752f7 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 13 Oct 2006 19:48:19 +0000 Subject: [PATCH] fix hang, etc --- ChangeLog | 17 ++++++++++++++++- kernel/dir.c | 31 ++++++++++++++++++++++--------- kernel/file.c | 10 ++++++++-- kernel/inode.c | 5 ++++- lib/fuse.c | 6 +++--- 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index 822cff9..d209894 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,23 @@ 2006-10-13 Miklos Szeredi - * kernel: Fix compilation on patched 2.6.18 (fc6) and 2.6.9. + * kernel: Fix compilation on patched 2.6.18 (fc6) and 2.6.19. Report from David Shaw + * lib: Fix lost error on renaming a file. Introduced in version + 2.4.1. Report from David Shaw + + * kernel: Fix a rare hang on SMP/32bit on heavy filesystem + activity. The cause of the bug was that some calls to + i_size_write() were not protected by a lock, and hence + i_size_seqcount could become corrupted. This caused subsequent + calls to i_size_read() to spin forever. This is a long standing + bug was probably introduced in version 2.2, and thought to be + related to NFS exporting (it's not). It was reported by various + people, but Dana Henriksen has finally helped me to track it down, + so big thanks to him + + * kernel: Protect against truncation of a swapfile + 2006-10-10 Miklos Szeredi * kernel: Check for signature of super_operations->umount_begin(). diff --git a/kernel/dir.c b/kernel/dir.c index 3f12e19..b45d514 100644 --- a/kernel/dir.c +++ b/kernel/dir.c @@ -992,14 +992,29 @@ static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg) #endif } +static void fuse_vmtruncate(struct inode *inode, loff_t offset) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + loff_t origsize = i_size_read(inode); + if (origsize != offset) { + spin_lock(&fc->lock); + i_size_write(inode, offset); + spin_unlock(&fc->lock); + } + if (origsize > offset) { + struct address_space *mapping = inode->i_mapping; + unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1); + truncate_inode_pages(mapping, offset); + } +} + /* * Set attributes, and at the same time refresh them. * * Truncation is slightly complicated, because the 'truncate' request * may fail, in which case we don't want to touch the mapping. - * vmtruncate() doesn't allow for this case. So do the rlimit - * checking by hand and call vmtruncate() only after the file has - * actually been truncated. + * vmtruncate() doesn't allow for this case, so do the rlimit checking + * and the actual truncation by hand. */ static int fuse_setattr(struct dentry *entry, struct iattr *attr) { @@ -1030,6 +1045,8 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) send_sig(SIGXFSZ, current, 0); return -EFBIG; } + if (IS_SWAPFILE(inode)) + return -ETXTBSY; } req = fuse_get_req(fc); @@ -1059,12 +1076,8 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) #endif err = -EIO; } else { - if (is_truncate) { - loff_t origsize = i_size_read(inode); - i_size_write(inode, outarg.attr.size); - if (origsize > outarg.attr.size) - vmtruncate(inode, outarg.attr.size); - } + if (is_truncate) + fuse_vmtruncate(inode, outarg.attr.size); fuse_change_attributes(inode, &outarg.attr); fi->i_time = time_to_jiffies(outarg.attr_valid, outarg.attr_valid_nsec); diff --git a/kernel/file.c b/kernel/file.c index b9d86ed..0470069 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -495,8 +495,11 @@ static int fuse_commit_write(struct file *file, struct page *page, err = -EIO; if (!err) { pos += count; - if (pos > i_size_read(inode)) + if (pos > i_size_read(inode)) { + spin_lock(&fc->lock); i_size_write(inode, pos); + spin_unlock(&fc->lock); + } if (offset == 0 && to == PAGE_CACHE_SIZE) { clear_page_dirty(page); @@ -601,8 +604,11 @@ static ssize_t fuse_direct_io(struct file *file, const char __user *buf, } fuse_put_request(fc, req); if (res > 0) { - if (write && pos > i_size_read(inode)) + if (write && pos > i_size_read(inode)) { + spin_lock(&fc->lock); i_size_write(inode, pos); + spin_unlock(&fc->lock); + } *ppos = pos; } fuse_invalidate_attr(inode); diff --git a/kernel/inode.c b/kernel/inode.c index e3f98db..d70141b 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -125,6 +125,7 @@ static int fuse_remount_fs(struct super_block *sb, int *flags, char *data) void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) { + struct fuse_conn *fc = get_fuse_conn(inode); if (S_ISREG(inode->i_mode) && i_size_read(inode) != attr->size) invalidate_inode_pages(inode->i_mapping); @@ -133,7 +134,9 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) inode->i_nlink = attr->nlink; inode->i_uid = attr->uid; inode->i_gid = attr->gid; + spin_lock(&fc->lock); i_size_write(inode, attr->size); + spin_unlock(&fc->lock); #ifdef HAVE_I_BLKSIZE inode->i_blksize = PAGE_CACHE_SIZE; #endif @@ -149,7 +152,7 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) { inode->i_mode = attr->mode & S_IFMT; - i_size_write(inode, attr->size); + inode->i_size = attr->size; if (S_ISREG(inode->i_mode)) { fuse_init_common(inode); fuse_init_file_inode(inode); diff --git a/lib/fuse.c b/lib/fuse.c index 8fc1bac..6de98fa 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -701,8 +701,8 @@ static int hide_node(struct fuse *f, fuse_req_t req, const char *oldpath, if (f->op.rename && f->op.unlink) { newpath = hidden_name(f, req, dir, oldname, newname, sizeof(newname)); if (newpath) { - int res = fuse_do_rename(f, req, oldpath, newpath); - if (res == 0) + err = fuse_do_rename(f, req, oldpath, newpath); + if (!err) err = rename_node(f, dir, oldname, dir, newname, 1); free(newpath); } @@ -1346,7 +1346,7 @@ static void fuse_rename(fuse_req_t req, fuse_ino_t olddir, const char *oldname, is_open(f, newdir, newname)) err = hide_node(f, req, newpath, newdir, newname); if (!err) { - fuse_do_rename(f, req, oldpath, newpath); + err = fuse_do_rename(f, req, oldpath, newpath); if (!err) err = rename_node(f, olddir, oldname, newdir, newname, 0); } -- 2.30.2