📄 inode.c
字号:
* + if there is a block to the left of our position - allocate near it. * + if pointer will live in indirect block - allocate near that block. * + if pointer will live in inode - allocate in the same * cylinder group. * Caller must make sure that @ind is valid and will stay that way. */static inline unsigned long ext3_find_near(struct inode *inode, Indirect *ind){ u32 *start = ind->bh ? (u32*) ind->bh->b_data : inode->u.ext3_i.i_data; u32 *p; /* Try to find previous block */ for (p = ind->p - 1; p >= start; p--) if (*p) return le32_to_cpu(*p); /* No such thing, so let's try location of indirect block */ if (ind->bh) return ind->bh->b_blocknr; /* * It is going to be refered from inode itself? OK, just put it into * the same cylinder group then. */ return (inode->u.ext3_i.i_block_group * EXT3_BLOCKS_PER_GROUP(inode->i_sb)) + le32_to_cpu(inode->i_sb->u.ext3_sb.s_es->s_first_data_block);}/** * ext3_find_goal - find a prefered place for allocation. * @inode: owner * @block: block we want * @chain: chain of indirect blocks * @partial: pointer to the last triple within a chain * @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. */static int ext3_find_goal(struct inode *inode, long block, Indirect chain[4], Indirect *partial, unsigned long *goal){ /* Writer: ->i_next_alloc* */ if (block == inode->u.ext3_i.i_next_alloc_block + 1) { inode->u.ext3_i.i_next_alloc_block++; inode->u.ext3_i.i_next_alloc_goal++; }#ifdef SEARCH_FROM_ZERO inode->u.ext3_i.i_next_alloc_block = 0; inode->u.ext3_i.i_next_alloc_goal = 0;#endif /* Writer: end */ /* Reader: pointers, ->i_next_alloc* */ if (verify_chain(chain, partial)) { /* * try the heuristic for sequential allocation, * failing that at least try to get decent locality. */ if (block == inode->u.ext3_i.i_next_alloc_block) *goal = inode->u.ext3_i.i_next_alloc_goal; if (!*goal) *goal = ext3_find_near(inode, partial);#ifdef SEARCH_FROM_ZERO *goal = 0;#endif return 0; } /* Reader: end */ return -EAGAIN;}/** * ext3_alloc_branch - allocate and set up a chain of blocks. * @inode: owner * @num: depth of the chain (number of blocks to allocate) * @offsets: offsets (in the blocks) to store the pointers to next. * @branch: place to store the chain in. * * This function allocates @num blocks, zeroes out all but the last one, * links them into chain and (if we are synchronous) writes them to disk. * In other words, it prepares a branch that can be spliced onto the * inode. It stores the information about that chain in the branch[], in * the same format as ext3_get_branch() would do. We are calling it after * we had read the existing part of chain and partial points to the last * triple of that (one with zero ->key). Upon the exit we have the same * picture as after the successful ext3_get_block(), excpet that in one * place chain is disconnected - *branch->p is still zero (we did not * set the last link), but branch->key contains the number that should * be placed into *branch->p to fill that gap. * * If allocation fails we free all blocks we've allocated (and forget * their buffer_heads) and return the error value the from failed * ext3_alloc_block() (normally -ENOSPC). Otherwise we set the chain * as described above and return 0. */static int ext3_alloc_branch(handle_t *handle, struct inode *inode, int num, unsigned long goal, int *offsets, Indirect *branch){ int blocksize = inode->i_sb->s_blocksize; int n = 0, keys = 0; int err = 0; int i; int parent = ext3_alloc_block(handle, inode, goal, &err); branch[0].key = cpu_to_le32(parent); if (parent) { for (n = 1; n < num; n++) { struct buffer_head *bh; /* Allocate the next block */ int nr = ext3_alloc_block(handle, inode, parent, &err); if (!nr) break; branch[n].key = cpu_to_le32(nr); keys = n+1; /* * Get buffer_head for parent block, zero it out * and set the pointer to new one, then send * parent to disk. */ bh = sb_getblk(inode->i_sb, parent); branch[n].bh = bh; lock_buffer(bh); BUFFER_TRACE(bh, "call get_create_access"); err = ext3_journal_get_create_access(handle, bh); if (err) { unlock_buffer(bh); brelse(bh); break; } memset(bh->b_data, 0, blocksize); branch[n].p = (u32*) bh->b_data + offsets[n]; *branch[n].p = branch[n].key; BUFFER_TRACE(bh, "marking uptodate"); mark_buffer_uptodate(bh, 1); unlock_buffer(bh); BUFFER_TRACE(bh, "call ext3_journal_dirty_metadata"); err = ext3_journal_dirty_metadata(handle, bh); if (err) break; parent = nr; } if (IS_SYNC(inode)) handle->h_sync = 1; } if (n == num) return 0; /* Allocation failed, free what we already allocated */ for (i = 1; i < keys; i++) { BUFFER_TRACE(branch[i].bh, "call journal_forget"); ext3_journal_forget(handle, branch[i].bh); } for (i = 0; i < keys; i++) ext3_free_blocks(handle, inode, le32_to_cpu(branch[i].key), 1); return err;}/** * ext3_splice_branch - splice the allocated branch onto inode. * @inode: owner * @block: (logical) number of block we are adding * @chain: chain of indirect blocks (with a missing link - see * ext3_alloc_branch) * @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 * 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. */static int ext3_splice_branch(handle_t *handle, struct inode *inode, long block, Indirect chain[4], Indirect *where, int num){ int i; int err = 0; /* * If we're splicing into a [td]indirect block (as opposed to the * inode) then we need to get write access to the [td]indirect block * before the splice. */ if (where->bh) { BUFFER_TRACE(where->bh, "get_write_access"); err = ext3_journal_get_write_access(handle, where->bh); if (err) goto err_out; } /* Verify that place we are splicing to is still there and vacant */ /* Writer: pointers, ->i_next_alloc* */ if (!verify_chain(chain, where-1) || *where->p) /* Writer: end */ goto changed; /* That's it */ *where->p = where->key; inode->u.ext3_i.i_next_alloc_block = block; inode->u.ext3_i.i_next_alloc_goal = le32_to_cpu(where[num-1].key);#ifdef SEARCH_FROM_ZERO inode->u.ext3_i.i_next_alloc_block = 0; inode->u.ext3_i.i_next_alloc_goal = 0;#endif /* Writer: end */ /* We are done with atomic stuff, now do the rest of housekeeping */ inode->i_ctime = CURRENT_TIME; ext3_mark_inode_dirty(handle, inode); /* had we spliced it onto indirect block? */ if (where->bh) { /* * akpm: If we spliced it onto an indirect block, we haven't * altered the inode. Note however that if it is being spliced * onto an indirect block at the very end of the file (the * file is growing) then we *will* alter the inode to reflect * the new i_size. But that is not done here - it is done in * generic_commit_write->__mark_inode_dirty->ext3_dirty_inode. */ jbd_debug(5, "splicing indirect only\n"); BUFFER_TRACE(where->bh, "call ext3_journal_dirty_metadata"); err = ext3_journal_dirty_metadata(handle, where->bh); if (err) goto err_out; } else { /* * OK, we spliced it into the inode itself on a direct block. * Inode was dirtied above. */ jbd_debug(5, "splicing direct\n"); } 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;}/* * Allocation strategy is simple: if we have to allocate something, we will * have to go the whole way to leaf. So let's do it before attaching anything * to tree, set linkage between the newborn blocks, write them if sync is * required, recheck the path, free and repeat if check fails, otherwise * set the last missing link (that will protect us from any truncate-generated * removals - all blocks on the path are immune now) and possibly force the * write on the parent block. * That has a nice additional property: no special recovery from the failed * allocations is needed - we simply release blocks and do not touch anything * reachable from inode. * * akpm: `handle' can be NULL if create == 0. * * The BKL may not be held on entry here. Be sure to take it early. */static int ext3_get_block_handle(handle_t *handle, struct inode *inode, long iblock, struct buffer_head *bh_result, int create){ int err = -EIO; int offsets[4]; Indirect chain[4]; Indirect *partial; unsigned long goal; int left; int depth = ext3_block_to_path(inode, iblock, offsets); loff_t new_size; J_ASSERT(handle != NULL || create == 0); if (depth == 0) goto out; lock_kernel();reread: partial = ext3_get_branch(inode, depth, offsets, chain, &err); /* Simplest case - block found, no allocation needed */ if (!partial) { bh_result->b_state &= ~(1UL << BH_New);got_it: bh_result->b_dev = inode->i_dev; bh_result->b_blocknr = le32_to_cpu(chain[depth-1].key); bh_result->b_state |= (1UL << BH_Mapped); /* Clean up and exit */ partial = chain+depth-1; /* the whole chain */ goto cleanup; } /* Next simple case - plain lookup or failed read of indirect block */ if (!create || err == -EIO) {cleanup: while (partial > chain) { BUFFER_TRACE(partial->bh, "call brelse"); brelse(partial->bh); partial--; } BUFFER_TRACE(bh_result, "returned"); unlock_kernel();out: return err; } /* * 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; if (ext3_find_goal(inode, iblock, chain, partial, &goal) < 0) goto changed; left = (chain + depth) - partial; /* * Block out ext3_truncate while we alter the tree */ down_read(&inode->u.ext3_i.truncate_sem); err = ext3_alloc_branch(handle, inode, left, goal, offsets+(partial-chain), partial); /* 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 */ if (!err) err = ext3_splice_branch(handle, inode, iblock, chain, partial, left); up_read(&inode->u.ext3_i.truncate_sem); if (err == -EAGAIN) goto changed; if (err) goto cleanup; new_size = inode->i_size; /* * This is not racy against ext3_truncate's modification of i_disksize * because VM/VFS ensures that the file cannot be extended while * truncate is in progress. It is racy between multiple parallel * instances of get_block, but we have the BKL. */ if (new_size > inode->u.ext3_i.i_disksize) inode->u.ext3_i.i_disksize = new_size; bh_result->b_state |= (1UL << BH_New); goto got_it;changed: while (partial > chain) { jbd_debug(1, "buffer chain changed, retrying\n"); BUFFER_TRACE(partial->bh, "brelsing"); brelse(partial->bh); partial--; } goto reread;}/* * The BKL is not held on entry here. */static int ext3_get_block(struct inode *inode, long iblock, struct buffer_head *bh_result, int create){ handle_t *handle = 0; int ret; if (create) { handle = ext3_journal_current_handle(); J_ASSERT(handle != 0); } ret = ext3_get_block_handle(handle, inode, iblock, bh_result, create); return ret;}/* * `handle' can be NULL if create is zero */struct buffer_head *ext3_getblk(handle_t *handle, struct inode * inode, long block, int create, int * errp){ struct buffer_head dummy; int fatal = 0, err; J_ASSERT(handle != NULL || create == 0); dummy.b_state = 0; dummy.b_blocknr = -1000; buffer_trace_init(&dummy.b_history); *errp = ext3_get_block_handle(handle, inode, block, &dummy, create); if (!*errp && buffer_mapped(&dummy)) { struct buffer_head *bh; bh = sb_getblk(inode->i_sb, dummy.b_blocknr);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -