diff options
Diffstat (limited to 'fs/ext3')
-rw-r--r-- | fs/ext3/inode.c | 144 |
1 files changed, 61 insertions, 83 deletions
diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c index 040eb288bb1c..ea5888688f94 100644 --- a/fs/ext3/inode.c +++ b/fs/ext3/inode.c @@ -455,12 +455,11 @@ static unsigned long ext3_find_near(struct inode *inode, Indirect *ind) * @goal: place to store the result. * * Normally this function find the prefered place for block allocation, - * stores it in *@goal and returns zero. If the branch had been changed - * under us we return -EAGAIN. + * stores it in *@goal and returns zero. */ -static int ext3_find_goal(struct inode *inode, long block, Indirect chain[4], - Indirect *partial, unsigned long *goal) +static unsigned long ext3_find_goal(struct inode *inode, long block, + Indirect chain[4], Indirect *partial) { struct ext3_block_alloc_info *block_i = EXT3_I(inode)->i_block_alloc_info; @@ -470,15 +469,10 @@ static int ext3_find_goal(struct inode *inode, long block, Indirect chain[4], */ if (block_i && (block == block_i->last_alloc_logical_block + 1) && (block_i->last_alloc_physical_block != 0)) { - *goal = block_i->last_alloc_physical_block + 1; - return 0; + return block_i->last_alloc_physical_block + 1; } - if (verify_chain(chain, partial)) { - *goal = ext3_find_near(inode, partial); - return 0; - } - return -EAGAIN; + return ext3_find_near(inode, partial); } /** @@ -582,12 +576,9 @@ static int ext3_alloc_branch(handle_t *handle, struct inode *inode, * @where: location of missing link * @num: number of blocks we are adding * - * This function verifies that chain (up to the missing link) had not - * changed, fills the missing link and does all housekeeping needed in + * This function fills the missing link and does all housekeeping needed in * inode (->i_blocks, etc.). In case of success we end up with the full - * chain to new block and return 0. Otherwise (== chain had been changed) - * we free the new blocks (forgetting their buffer_heads, indeed) and - * return -EAGAIN. + * chain to new block and return 0. */ static int ext3_splice_branch(handle_t *handle, struct inode *inode, long block, @@ -608,12 +599,6 @@ static int ext3_splice_branch(handle_t *handle, struct inode *inode, long block, if (err) goto err_out; } - /* Verify that place we are splicing to is still there and vacant */ - - if (!verify_chain(chain, where-1) || *where->p) - /* Writer: end */ - goto changed; - /* That's it */ *where->p = where->key; @@ -657,26 +642,11 @@ static int ext3_splice_branch(handle_t *handle, struct inode *inode, long block, } return err; -changed: - /* - * AKPM: if where[i].bh isn't part of the current updating - * transaction then we explode nastily. Test this code path. - */ - jbd_debug(1, "the chain changed: try again\n"); - err = -EAGAIN; - err_out: for (i = 1; i < num; i++) { BUFFER_TRACE(where[i].bh, "call journal_forget"); ext3_journal_forget(handle, where[i].bh); } - /* For the normal collision cleanup case, we free up the blocks. - * On genuine filesystem errors we don't even think about doing - * that. */ - if (err == -EAGAIN) - for (i = 0; i < num; i++) - ext3_free_blocks(handle, inode, - le32_to_cpu(where[i].key), 1); return err; } @@ -708,7 +678,7 @@ ext3_get_block_handle(handle_t *handle, struct inode *inode, sector_t iblock, unsigned long goal; int left; int boundary = 0; - int depth = ext3_block_to_path(inode, iblock, offsets, &boundary); + const int depth = ext3_block_to_path(inode, iblock, offsets, &boundary); struct ext3_inode_info *ei = EXT3_I(inode); J_ASSERT(handle != NULL || create == 0); @@ -716,54 +686,55 @@ ext3_get_block_handle(handle_t *handle, struct inode *inode, sector_t iblock, if (depth == 0) goto out; -reread: partial = ext3_get_branch(inode, depth, offsets, chain, &err); /* Simplest case - block found, no allocation needed */ if (!partial) { clear_buffer_new(bh_result); -got_it: - map_bh(bh_result, inode->i_sb, le32_to_cpu(chain[depth-1].key)); - if (boundary) - set_buffer_boundary(bh_result); - /* Clean up and exit */ - partial = chain+depth-1; /* the whole chain */ - goto cleanup; + goto got_it; } /* Next simple case - plain lookup or failed read of indirect block */ - if (!create || err == -EIO) { -cleanup: + if (!create || err == -EIO) + goto cleanup; + + down(&ei->truncate_sem); + + /* + * If the indirect block is missing while we are reading + * the chain(ext3_get_branch() returns -EAGAIN err), or + * if the chain has been changed after we grab the semaphore, + * (either because another process truncated this branch, or + * another get_block allocated this branch) re-grab the chain to see if + * the request block has been allocated or not. + * + * Since we already block the truncate/other get_block + * at this point, we will have the current copy of the chain when we + * splice the branch into the tree. + */ + if (err == -EAGAIN || !verify_chain(chain, partial)) { while (partial > chain) { - BUFFER_TRACE(partial->bh, "call brelse"); brelse(partial->bh); partial--; } - BUFFER_TRACE(bh_result, "returned"); -out: - return err; + partial = ext3_get_branch(inode, depth, offsets, chain, &err); + if (!partial) { + up(&ei->truncate_sem); + if (err) + goto cleanup; + clear_buffer_new(bh_result); + goto got_it; + } } /* - * Indirect block might be removed by truncate while we were - * reading it. Handling of that case (forget what we've got and - * reread) is taken out of the main path. - */ - if (err == -EAGAIN) - goto changed; - - goal = 0; - down(&ei->truncate_sem); - - /* lazy initialize the block allocation info here if necessary */ - if (S_ISREG(inode->i_mode) && (!ei->i_block_alloc_info)) { + * Okay, we need to do block allocation. Lazily initialize the block + * allocation info here if necessary + */ + if (S_ISREG(inode->i_mode) && (!ei->i_block_alloc_info)) ext3_init_block_alloc_info(inode); - } - if (ext3_find_goal(inode, iblock, chain, partial, &goal) < 0) { - up(&ei->truncate_sem); - goto changed; - } + goal = ext3_find_goal(inode, iblock, chain, partial); left = (chain + depth) - partial; @@ -771,38 +742,45 @@ out: * Block out ext3_truncate while we alter the tree */ err = ext3_alloc_branch(handle, inode, left, goal, - offsets+(partial-chain), partial); + offsets + (partial - chain), partial); - /* The ext3_splice_branch call will free and forget any buffers + /* + * The ext3_splice_branch call will free and forget any buffers * on the new chain if there is a failure, but that risks using * up transaction credits, especially for bitmaps where the * credits cannot be returned. Can we handle this somehow? We - * may need to return -EAGAIN upwards in the worst case. --sct */ + * may need to return -EAGAIN upwards in the worst case. --sct + */ if (!err) err = ext3_splice_branch(handle, inode, iblock, chain, partial, left); - /* i_disksize growing is protected by truncate_sem - * don't forget to protect it if you're about to implement - * concurrent ext3_get_block() -bzzz */ + /* + * i_disksize growing is protected by truncate_sem. Don't forget to + * protect it if you're about to implement concurrent + * ext3_get_block() -bzzz + */ if (!err && extend_disksize && inode->i_size > ei->i_disksize) ei->i_disksize = inode->i_size; up(&ei->truncate_sem); - if (err == -EAGAIN) - goto changed; if (err) goto cleanup; set_buffer_new(bh_result); - goto got_it; - -changed: +got_it: + map_bh(bh_result, inode->i_sb, le32_to_cpu(chain[depth-1].key)); + if (boundary) + set_buffer_boundary(bh_result); + /* Clean up and exit */ + partial = chain + depth - 1; /* the whole chain */ +cleanup: while (partial > chain) { - jbd_debug(1, "buffer chain changed, retrying\n"); - BUFFER_TRACE(partial->bh, "brelsing"); + BUFFER_TRACE(partial->bh, "call brelse"); brelse(partial->bh); partial--; } - goto reread; + BUFFER_TRACE(bh_result, "returned"); +out: + return err; } static int ext3_get_block(struct inode *inode, sector_t iblock, |