summaryrefslogtreecommitdiffstats
path: root/fs/cifs/inode.c
diff options
context:
space:
mode:
authorSteve French <sfrench@us.ibm.com>2007-02-26 16:46:11 +0000
committerSteve French <sfrench@us.ibm.com>2007-02-26 16:46:11 +0000
commit3677db10a635a39f63ea509f8f0056d95589ff90 (patch)
tree5256a408110c91947d9b9543199003fb976948a8 /fs/cifs/inode.c
parent9654640d0af8f2de40ff3807d3695109d3463f54 (diff)
downloadblackbird-op-linux-3677db10a635a39f63ea509f8f0056d95589ff90.tar.gz
blackbird-op-linux-3677db10a635a39f63ea509f8f0056d95589ff90.zip
[CIFS] Fix locking problem around some cifs uses of i_size write
Could cause hangs on smp systems in i_size_read on a cifs inode whose size has been previously simultaneously updated from different processes. Thanks to Brian Wang for some great testing/debugging on this hard problem. Fixes kernel bugzilla #7903 CC: Shirish Pargoankar <shirishp@us.ibm.com> CC: Shaggy <shaggy@us.ibm.com> Signed-off-by: Steve French <sfrench@us.ibm.com>
Diffstat (limited to 'fs/cifs/inode.c')
-rw-r--r--fs/cifs/inode.c50
1 files changed, 48 insertions, 2 deletions
diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
index 37c6ce87416b..24df13a256e5 100644
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -143,10 +143,10 @@ int cifs_get_inode_info_unix(struct inode **pinode,
inode->i_gid = le64_to_cpu(findData.Gid);
inode->i_nlink = le64_to_cpu(findData.Nlinks);
+ spin_lock(&inode->i_lock);
if (is_size_safe_to_change(cifsInfo, end_of_file)) {
/* can not safely change the file size here if the
client is writing to it due to potential races */
-
i_size_write(inode, end_of_file);
/* blksize needs to be multiple of two. So safer to default to
@@ -162,6 +162,7 @@ int cifs_get_inode_info_unix(struct inode **pinode,
/* for this calculation */
inode->i_blocks = (512 - 1 + num_of_bytes) >> 9;
}
+ spin_unlock(&inode->i_lock);
if (num_of_bytes < end_of_file)
cFYI(1, ("allocation size less than end of file"));
@@ -496,6 +497,8 @@ int cifs_get_inode_info(struct inode **pinode,
/* BB add code here -
validate if device or weird share or device type? */
}
+
+ spin_lock(&inode->i_lock);
if (is_size_safe_to_change(cifsInfo, le64_to_cpu(pfindData->EndOfFile))) {
/* can not safely shrink the file size here if the
client is writing to it due to potential races */
@@ -506,6 +509,7 @@ int cifs_get_inode_info(struct inode **pinode,
inode->i_blocks = (512 - 1 + le64_to_cpu(
pfindData->AllocationSize)) >> 9;
}
+ spin_unlock(&inode->i_lock);
inode->i_nlink = le32_to_cpu(pfindData->NumberOfLinks);
@@ -834,8 +838,10 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
if (!rc) {
drop_nlink(inode);
+ spin_lock(&direntry->d_inode->i_lock);
i_size_write(direntry->d_inode,0);
clear_nlink(direntry->d_inode);
+ spin_unlock(&direntry->d_inode->i_lock);
}
cifsInode = CIFS_I(direntry->d_inode);
@@ -1128,6 +1134,46 @@ static int cifs_truncate_page(struct address_space *mapping, loff_t from)
return rc;
}
+int cifs_vmtruncate(struct inode * inode, loff_t offset)
+{
+ struct address_space *mapping = inode->i_mapping;
+ unsigned long limit;
+
+ if (inode->i_size < offset)
+ goto do_expand;
+ /*
+ * truncation of in-use swapfiles is disallowed - it would cause
+ * subsequent swapout to scribble on the now-freed blocks.
+ */
+ if (IS_SWAPFILE(inode))
+ goto out_busy;
+ spin_lock(&inode->i_lock);
+ i_size_write(inode, offset);
+ spin_unlock(&inode->i_lock);
+ unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1);
+ truncate_inode_pages(mapping, offset);
+ goto out_truncate;
+
+do_expand:
+ limit = current->signal->rlim[RLIMIT_FSIZE].rlim_cur;
+ if (limit != RLIM_INFINITY && offset > limit)
+ goto out_sig;
+ if (offset > inode->i_sb->s_maxbytes)
+ goto out_big;
+ i_size_write(inode, offset);
+
+out_truncate:
+ if (inode->i_op && inode->i_op->truncate)
+ inode->i_op->truncate(inode);
+ return 0;
+out_sig:
+ send_sig(SIGXFSZ, current, 0);
+out_big:
+ return -EFBIG;
+out_busy:
+ return -ETXTBSY;
+}
+
int cifs_setattr(struct dentry *direntry, struct iattr *attrs)
{
int xid;
@@ -1244,7 +1290,7 @@ int cifs_setattr(struct dentry *direntry, struct iattr *attrs)
*/
if (rc == 0) {
- rc = vmtruncate(direntry->d_inode, attrs->ia_size);
+ rc = cifs_vmtruncate(direntry->d_inode, attrs->ia_size);
cifs_truncate_page(direntry->d_inode->i_mapping,
direntry->d_inode->i_size);
} else
OpenPOWER on IntegriCloud