diff options
author | Dmitry Monakhov <dmonakhov@openvz.org> | 2010-03-02 08:08:51 -0500 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2010-03-02 08:08:51 -0500 |
commit | 67eeb5685d2a211c0252ac7884142e503c759500 (patch) | |
tree | 48f01e99b3cd52e20d5ac253ddd87e45a0a61af0 /fs/ext4/super.c | |
parent | 273df556b6ee2065bfe96edab5888d3dc9b108d8 (diff) |
ext4: Fix ext4_quota_write cross block boundary behaviour
We always assume what dquot update result in changes in one data block
But ext4_quota_write() function may handle cross block boundary writes
In fact if this ever happen it will result in incorrect journal
credits reservation, and later a BUG_ON. As soon this never happen
the boundary cross loop is NOOP. In order to make things straight
let's remove this loop and assert cross boundary condition.
Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4/super.c')
-rw-r--r-- | fs/ext4/super.c | 69 |
1 files changed, 34 insertions, 35 deletions
diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 5a18e9ec7cf9..ad1ee5f21bab 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c | |||
@@ -4010,9 +4010,7 @@ static ssize_t ext4_quota_write(struct super_block *sb, int type, | |||
4010 | ext4_lblk_t blk = off >> EXT4_BLOCK_SIZE_BITS(sb); | 4010 | ext4_lblk_t blk = off >> EXT4_BLOCK_SIZE_BITS(sb); |
4011 | int err = 0; | 4011 | int err = 0; |
4012 | int offset = off & (sb->s_blocksize - 1); | 4012 | int offset = off & (sb->s_blocksize - 1); |
4013 | int tocopy; | ||
4014 | int journal_quota = EXT4_SB(sb)->s_qf_names[type] != NULL; | 4013 | int journal_quota = EXT4_SB(sb)->s_qf_names[type] != NULL; |
4015 | size_t towrite = len; | ||
4016 | struct buffer_head *bh; | 4014 | struct buffer_head *bh; |
4017 | handle_t *handle = journal_current_handle(); | 4015 | handle_t *handle = journal_current_handle(); |
4018 | 4016 | ||
@@ -4022,52 +4020,53 @@ static ssize_t ext4_quota_write(struct super_block *sb, int type, | |||
4022 | (unsigned long long)off, (unsigned long long)len); | 4020 | (unsigned long long)off, (unsigned long long)len); |
4023 | return -EIO; | 4021 | return -EIO; |
4024 | } | 4022 | } |
4023 | /* | ||
4024 | * Since we account only one data block in transaction credits, | ||
4025 | * then it is impossible to cross a block boundary. | ||
4026 | */ | ||
4027 | if (sb->s_blocksize - offset < len) { | ||
4028 | ext4_msg(sb, KERN_WARNING, "Quota write (off=%llu, len=%llu)" | ||
4029 | " cancelled because not block aligned", | ||
4030 | (unsigned long long)off, (unsigned long long)len); | ||
4031 | return -EIO; | ||
4032 | } | ||
4033 | |||
4025 | mutex_lock_nested(&inode->i_mutex, I_MUTEX_QUOTA); | 4034 | mutex_lock_nested(&inode->i_mutex, I_MUTEX_QUOTA); |
4026 | while (towrite > 0) { | 4035 | bh = ext4_bread(handle, inode, blk, 1, &err); |
4027 | tocopy = sb->s_blocksize - offset < towrite ? | 4036 | if (!bh) |
4028 | sb->s_blocksize - offset : towrite; | 4037 | goto out; |
4029 | bh = ext4_bread(handle, inode, blk, 1, &err); | 4038 | if (journal_quota) { |
4030 | if (!bh) | 4039 | err = ext4_journal_get_write_access(handle, bh); |
4040 | if (err) { | ||
4041 | brelse(bh); | ||
4031 | goto out; | 4042 | goto out; |
4032 | if (journal_quota) { | ||
4033 | err = ext4_journal_get_write_access(handle, bh); | ||
4034 | if (err) { | ||
4035 | brelse(bh); | ||
4036 | goto out; | ||
4037 | } | ||
4038 | } | 4043 | } |
4039 | lock_buffer(bh); | ||
4040 | memcpy(bh->b_data+offset, data, tocopy); | ||
4041 | flush_dcache_page(bh->b_page); | ||
4042 | unlock_buffer(bh); | ||
4043 | if (journal_quota) | ||
4044 | err = ext4_handle_dirty_metadata(handle, NULL, bh); | ||
4045 | else { | ||
4046 | /* Always do at least ordered writes for quotas */ | ||
4047 | err = ext4_jbd2_file_inode(handle, inode); | ||
4048 | mark_buffer_dirty(bh); | ||
4049 | } | ||
4050 | brelse(bh); | ||
4051 | if (err) | ||
4052 | goto out; | ||
4053 | offset = 0; | ||
4054 | towrite -= tocopy; | ||
4055 | data += tocopy; | ||
4056 | blk++; | ||
4057 | } | 4044 | } |
4045 | lock_buffer(bh); | ||
4046 | memcpy(bh->b_data+offset, data, len); | ||
4047 | flush_dcache_page(bh->b_page); | ||
4048 | unlock_buffer(bh); | ||
4049 | if (journal_quota) | ||
4050 | err = ext4_handle_dirty_metadata(handle, NULL, bh); | ||
4051 | else { | ||
4052 | /* Always do at least ordered writes for quotas */ | ||
4053 | err = ext4_jbd2_file_inode(handle, inode); | ||
4054 | mark_buffer_dirty(bh); | ||
4055 | } | ||
4056 | brelse(bh); | ||
4058 | out: | 4057 | out: |
4059 | if (len == towrite) { | 4058 | if (err) { |
4060 | mutex_unlock(&inode->i_mutex); | 4059 | mutex_unlock(&inode->i_mutex); |
4061 | return err; | 4060 | return err; |
4062 | } | 4061 | } |
4063 | if (inode->i_size < off+len-towrite) { | 4062 | if (inode->i_size < off + len) { |
4064 | i_size_write(inode, off+len-towrite); | 4063 | i_size_write(inode, off + len); |
4065 | EXT4_I(inode)->i_disksize = inode->i_size; | 4064 | EXT4_I(inode)->i_disksize = inode->i_size; |
4066 | } | 4065 | } |
4067 | inode->i_mtime = inode->i_ctime = CURRENT_TIME; | 4066 | inode->i_mtime = inode->i_ctime = CURRENT_TIME; |
4068 | ext4_mark_inode_dirty(handle, inode); | 4067 | ext4_mark_inode_dirty(handle, inode); |
4069 | mutex_unlock(&inode->i_mutex); | 4068 | mutex_unlock(&inode->i_mutex); |
4070 | return len - towrite; | 4069 | return len; |
4071 | } | 4070 | } |
4072 | 4071 | ||
4073 | #endif | 4072 | #endif |