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