| /* |
| ext2_block_relocator.c -- ext2 block relocator |
| Copyright (C) 1998-2000 Free Software Foundation, Inc. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. |
| */ |
| |
| #include <config.h> |
| |
| #ifndef DISCOVER_ONLY |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "ext2.h" |
| |
| |
| /* This struct describes a single block that will be relocated. The |
| * block's original location is "num", and its new location is "dest". |
| * The block is presumebly referred to by some other block in the file |
| * system, which is recorded as "refblock". (Only one reference to |
| * the block is allowed by the block relocator.) "refoffset" describes |
| * the location within the refblock in which the block is referenced. |
| * "isindirect" is 0 for direct, 1 for single-indirect, 2 for |
| * double-indirect, etc. |
| * |
| * The algorithms in the file fill the entries of this struct in this order: |
| * num, refblock/refoffset/isindirectblock, dest. |
| */ |
| struct ext2_block_entry |
| { |
| blk_t num; |
| blk_t dest; |
| blk_t refblock; |
| unsigned refoffset:16; |
| unsigned isindirectblock:16; |
| }; |
| |
| /* This struct contains all data structures relevant to the block relocator. |
| * - newallocoffset is the distance between the start of a block group, |
| * and the first data block in the group. This can change when a |
| * filesystem is resized, because the size of the group descriptors is |
| * proportional to the size of the filesystem. |
| * |
| * - allocentries is the size of the "block" array. It is a tuneable |
| * parameter that determines how many blocks can be moved in each |
| * pass. |
| * |
| * - usedentries says how many entries of the "block" array have been |
| * used. That is, how many blocks have been scheduled so far to |
| * be moved. |
| * |
| * - resolvedentries is the number of blocks whose referencing block |
| * has been found and recorded in block[.]->refblock, etc. |
| * |
| * - block is an array that records which blocks need to be moved, and |
| * where they will be moved to, etc. At some point in the algorithm, this |
| * array gets sorted (grep for qsort!) by indirectness. |
| * |
| * - start: each entry in this array corresponds to a level of |
| * indirectness (0-3). Each level has two items: dst and num. "num" |
| * is the number of blocks inside "block" of that level of indirectness. |
| * After doscan() is finished, and the level of indirectness of each |
| * block is known, "block" is sorted (see above). The "dst" pointer |
| * is a pointer inside "block" that indicates the start of the portion |
| * of the array containg blocks of that level of indirectness. |
| */ |
| struct ext2_block_relocator_state |
| { |
| blk_t newallocoffset; |
| blk_t allocentries; |
| blk_t usedentries; |
| blk_t resolvedentries; |
| struct ext2_block_entry *block; |
| |
| struct { |
| struct ext2_block_entry *dst; |
| int num; |
| } start[4]; |
| }; |
| |
| |
| |
| static int compare_block_entries(const void *x0, const void *x1) |
| { |
| const struct ext2_block_entry *b0; |
| const struct ext2_block_entry *b1; |
| |
| b0 = (const struct ext2_block_entry *)x0; |
| b1 = (const struct ext2_block_entry *)x1; |
| |
| if (b0->num < b1->num) |
| return -1; |
| |
| if (b0->num > b1->num) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int compare_block_entries_ind(const void *x0, const void *x1) |
| { |
| const struct ext2_block_entry *b0; |
| const struct ext2_block_entry *b1; |
| |
| b0 = (const struct ext2_block_entry *)x0; |
| b1 = (const struct ext2_block_entry *)x1; |
| |
| if (b0->isindirectblock > b1->isindirectblock) |
| return -1; |
| |
| if (b0->isindirectblock < b1->isindirectblock) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int compare_block_entries_ref(const void *x0, const void *x1) |
| { |
| const struct ext2_block_entry *b0; |
| const struct ext2_block_entry *b1; |
| |
| b0 = (const struct ext2_block_entry *)x0; |
| b1 = (const struct ext2_block_entry *)x1; |
| |
| if (b0->refblock < b1->refblock) |
| return -1; |
| |
| if (b0->refblock > b1->refblock) |
| return 1; |
| |
| return 0; |
| } |
| |
| struct ext2_block_entry *findit(struct ext2_block_relocator_state *state, blk_t block) |
| { |
| int min; |
| int max; |
| struct ext2_block_entry *retv; |
| int t; |
| blk_t tval; |
| |
| max = state->usedentries - 1; |
| min = 0; |
| retv = NULL; |
| |
| repeat: |
| if (min > max) |
| goto out; |
| |
| t = (min + max) >> 1; |
| tval = state->block[t].num; |
| |
| if (tval > block) |
| max = t - 1; |
| |
| if (tval < block) |
| min = t + 1; |
| |
| if (tval != block) |
| goto repeat; |
| |
| retv = &state->block[t]; |
| |
| out: |
| return retv; |
| } |
| |
| /* This function adds records a reference to a block ("blk"), if that |
| * block is scheduled to be moved. |
| */ |
| static int doblock(struct ext2_fs *fs, |
| struct ext2_block_relocator_state *state, |
| blk_t blk, |
| blk_t refblock, |
| off_t refoffset, |
| int indirect) |
| { |
| struct ext2_block_entry *ent; |
| |
| if ((ent = findit(state, blk)) == NULL) |
| return 1; |
| |
| if (ent->refblock) |
| { |
| ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, |
| _("Cross-linked blocks found! Better go run e2fsck " |
| "first!")); |
| return 0; |
| } |
| |
| ent->refblock = refblock; |
| ent->refoffset = refoffset; |
| ent->isindirectblock = indirect; |
| |
| state->resolvedentries++; |
| state->start[indirect].num++; |
| |
| return 1; |
| } |
| |
| static int doindblock(struct ext2_fs *fs, |
| struct ext2_block_relocator_state *state, |
| blk_t blk, |
| blk_t refblock, |
| off_t refoffset) |
| { |
| struct ext2_buffer_head *bh; |
| int i; |
| uint32_t *uptr; |
| |
| if (!doblock(fs, state, blk, refblock, refoffset, 1)) |
| return 0; |
| |
| bh = ext2_bread(fs, blk); |
| if (!bh) |
| return 0; |
| uptr = (uint32_t *)bh->data; |
| |
| for (i=0;i<(fs->blocksize >> 2);i++) |
| if (uptr[i]) |
| if (!doblock(fs, state, PED_LE32_TO_CPU(uptr[i]), blk, |
| i<<2, 0)) |
| return 0; |
| |
| if (!ext2_brelse(bh, 0)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int dodindblock(struct ext2_fs *fs, |
| struct ext2_block_relocator_state *state, |
| blk_t blk, |
| blk_t refblock, |
| off_t refoffset) |
| { |
| struct ext2_buffer_head *bh; |
| int i; |
| uint32_t *uptr; |
| |
| if (!doblock(fs, state, blk, refblock, refoffset, 2)) |
| return 0; |
| |
| bh = ext2_bread(fs, blk); |
| if (!bh) |
| return 0; |
| uptr = (uint32_t *)bh->data; |
| |
| for (i=0;i<(fs->blocksize >> 2);i++) |
| if (uptr[i]) |
| if (!doindblock(fs, state, PED_LE32_TO_CPU(uptr[i]), |
| blk, i<<2)) |
| return 0; |
| |
| if (!ext2_brelse(bh, 0)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int dotindblock(struct ext2_fs *fs, |
| struct ext2_block_relocator_state *state, |
| blk_t blk, |
| blk_t refblock, |
| off_t refoffset) |
| { |
| struct ext2_buffer_head *bh; |
| int i; |
| uint32_t *uptr; |
| |
| if (!doblock(fs, state, blk, refblock, refoffset, 3)) |
| return 0; |
| |
| bh = ext2_bread(fs, blk); |
| if (!bh) |
| return 0; |
| uptr = (uint32_t *)bh->data; |
| |
| for (i=0;i<(fs->blocksize >> 2);i++) |
| if (uptr[i]) |
| if (!dodindblock(fs, state, PED_LE32_TO_CPU(uptr[i]), |
| blk, i<<2)) |
| return 0; |
| |
| if (!ext2_brelse(bh, 0)) |
| return 0; |
| |
| return 1; |
| } |
| |
| |
| /* This function records any block references from an inode to blocks that are |
| * scheduled to be moved. |
| */ |
| static int doinode(struct ext2_fs *fs, struct ext2_block_relocator_state *state, int inode) |
| { |
| struct ext2_inode buf; |
| |
| if (!ext2_read_inode(fs, inode, &buf)) |
| return 0; |
| |
| if (EXT2_INODE_BLOCKS(buf)) |
| { |
| blk_t blk; |
| int i; |
| off_t inodeoffset; |
| blk_t inodeblock; |
| |
| inodeoffset = ext2_get_inode_offset(fs, inode, &inodeblock); |
| |
| /* do Hurd block, if there is one... */ |
| if (EXT2_SUPER_CREATOR_OS(fs->sb) == EXT2_OS_HURD |
| && EXT2_INODE_TRANSLATOR(buf)) { |
| if (!doblock(fs, |
| state, |
| EXT2_INODE_TRANSLATOR(buf), |
| inodeblock, |
| inodeoffset + offsetof(struct ext2_inode, |
| osd1.hurd1.h_i_translator), |
| 0)) |
| return 0; |
| } |
| |
| for (i=0;i<EXT2_NDIR_BLOCKS;i++) |
| if ((blk = EXT2_INODE_BLOCK(buf, i)) != 0) |
| if (!doblock(fs, |
| state, |
| blk, |
| inodeblock, |
| inodeoffset + offsetof(struct ext2_inode, i_block[i]), |
| 0)) |
| return 0; |
| |
| if ((blk = EXT2_INODE_BLOCK(buf, EXT2_IND_BLOCK)) != 0) |
| if (!doindblock(fs, |
| state, |
| blk, |
| inodeblock, |
| inodeoffset + offsetof(struct ext2_inode, i_block[EXT2_IND_BLOCK]))) |
| return 0; |
| |
| if ((blk = EXT2_INODE_BLOCK(buf, EXT2_DIND_BLOCK)) != 0) |
| if (!dodindblock(fs, |
| state, |
| blk, |
| inodeblock, |
| inodeoffset + offsetof(struct ext2_inode, i_block[EXT2_DIND_BLOCK]))) |
| return 0; |
| |
| if ((blk = EXT2_INODE_BLOCK(buf, EXT2_TIND_BLOCK)) != 0) |
| if (!dotindblock(fs, |
| state, |
| blk, |
| inodeblock, |
| inodeoffset + offsetof(struct ext2_inode, i_block[EXT2_TIND_BLOCK]))) |
| return 0; |
| |
| } |
| |
| return 1; |
| } |
| |
| /* This function scans the entire filesystem, to find all references to blocks |
| * that are scheduled to be moved. |
| */ |
| static int doscan(struct ext2_fs *fs, struct ext2_block_relocator_state *state) |
| { |
| int i; |
| |
| state->start[0].num = 0; |
| state->start[1].num = 0; |
| state->start[2].num = 0; |
| state->start[3].num = 0; |
| |
| for (i=0;i<fs->numgroups;i++) |
| { |
| struct ext2_buffer_head *bh; |
| unsigned int j; |
| int offset; |
| |
| if (fs->opt_verbose) |
| { |
| fprintf(stderr, " scanning group %i.... ", i); |
| fflush(stderr); |
| } |
| |
| bh = ext2_bread(fs, EXT2_GROUP_INODE_BITMAP(fs->gd[i])); |
| if (!bh) |
| return 0; |
| offset = i * EXT2_SUPER_INODES_PER_GROUP(fs->sb) + 1; |
| |
| for (j=0;j<EXT2_SUPER_INODES_PER_GROUP(fs->sb);j++) |
| if (bh->data[j>>3] & _bitmap[j&7]) |
| { |
| if (!doinode(fs, state, offset + j)) |
| { |
| ext2_brelse(bh, 0); |
| return 0; |
| } |
| |
| if (state->resolvedentries == state->usedentries) |
| break; |
| } |
| |
| ext2_brelse(bh, 0); |
| |
| if (fs->opt_verbose) |
| { |
| fprintf(stderr, "%i/%i blocks resolved\r", |
| state->resolvedentries, |
| state->usedentries); |
| fflush(stderr); |
| } |
| |
| if (state->resolvedentries == state->usedentries) |
| break; |
| } |
| |
| if (fs->opt_verbose) |
| fputc('\n', stderr); |
| |
| state->start[3].dst = state->block; |
| state->start[2].dst = state->start[3].dst + state->start[3].num; |
| state->start[1].dst = state->start[2].dst + state->start[2].num; |
| state->start[0].dst = state->start[1].dst + state->start[1].num; |
| |
| return 1; |
| } |
| |
| |
| |
| |
| |
| static int ext2_block_relocator_copy(struct ext2_fs *fs, struct ext2_block_relocator_state *state) |
| { |
| unsigned char *buf; |
| |
| ped_exception_fetch_all(); |
| buf = (unsigned char *) ped_malloc(MAXCONT << fs->logsize); |
| if (buf) |
| { |
| int num; |
| int numleft; |
| struct ext2_block_entry *ptr; |
| |
| ped_exception_leave_all(); |
| |
| numleft = state->usedentries; |
| ptr = state->block; |
| while (numleft) |
| { |
| num = PED_MIN(numleft, MAXCONT); |
| while (num != 1) |
| { |
| if (ptr[0].num + num-1 == ptr[num-1].num && |
| ptr[0].dest + num-1 == ptr[num-1].dest) |
| break; |
| |
| num >>= 1; |
| } |
| |
| if (!ext2_bcache_flush_range(fs, ptr[0].num, num)) |
| goto error_free_buf; |
| if (!ext2_bcache_flush_range(fs, ptr[0].dest, num)) |
| goto error_free_buf; |
| |
| if (!ext2_read_blocks(fs, buf, ptr[0].num, num)) |
| goto error_free_buf; |
| if (!ext2_write_blocks(fs, buf, ptr[0].dest, num)) |
| goto error_free_buf; |
| |
| ptr += num; |
| numleft -= num; |
| |
| if (fs->opt_verbose) |
| { |
| fprintf(stderr, "copied %i/%i blocks\r", |
| state->usedentries - numleft, |
| state->usedentries); |
| fflush(stderr); |
| } |
| } |
| |
| ped_free(buf); |
| |
| if (fs->opt_safe) |
| ext2_sync(fs); |
| |
| if (fs->opt_verbose) |
| fputc('\n', stderr); |
| } |
| else |
| { |
| blk_t i; |
| |
| ped_exception_catch(); |
| ped_exception_leave_all(); |
| |
| for (i=0;i<state->usedentries;i++) |
| { |
| struct ext2_block_entry *block; |
| |
| block = &state->block[i]; |
| if (!ext2_copy_block(fs, block->num, block->dest)) |
| goto error; |
| } |
| } |
| |
| return 1; |
| |
| error_free_buf: |
| ped_free(buf); |
| error: |
| return 0; |
| } |
| |
| static int ext2_block_relocator_ref(struct ext2_fs *fs, struct ext2_block_relocator_state *state, struct ext2_block_entry *block) |
| { |
| struct ext2_buffer_head *bh; |
| static int numerrors = 0; |
| |
| if (!(block->refblock || block->refoffset)) |
| { |
| ped_exception_throw (PED_EXCEPTION_BUG, PED_EXCEPTION_CANCEL, |
| _("Block %i has no reference? Weird."), |
| block->num); |
| return 0; |
| } |
| |
| bh = ext2_bread(fs, block->refblock); |
| if (!bh) |
| return 0; |
| |
| if (fs->opt_debug) |
| { |
| if (PED_LE32_TO_CPU(*((uint32_t *)(bh->data + block->refoffset))) |
| != block->num) { |
| fprintf(stderr, |
| "block %i ref error! (->%i {%i, %i})\n", |
| block->num, |
| block->dest, |
| block->refblock, |
| block->refoffset); |
| ext2_brelse(bh, 0); |
| |
| if (numerrors++ < 4) |
| return 1; |
| |
| fputs("all is not well!\n", stderr); |
| return 0; |
| } |
| } |
| |
| *((uint32_t *)(bh->data + block->refoffset)) |
| = PED_LE32_TO_CPU(block->dest); |
| bh->dirty = 1; |
| ext2_brelse(bh, 0); |
| |
| ext2_set_block_state(fs, block->dest, 1, 1); |
| ext2_set_block_state(fs, block->num, 0, 1); |
| |
| if (block->isindirectblock) |
| { |
| struct ext2_block_entry *dst; |
| int i; |
| int num; |
| |
| dst = state->start[block->isindirectblock-1].dst; |
| num = state->start[block->isindirectblock-1].num; |
| |
| for (i=0;i<num;i++) |
| if (dst[i].refblock == block->num) |
| dst[i].refblock = block->dest; |
| } |
| |
| return 1; |
| } |
| |
| /* This function allocates new locations for blocks that are scheduled to move |
| * (inside state->blocks). |
| * |
| * FIXME: doesn't seem to handle sparse block groups. That is, there might be |
| * some free space that could be exploited in resizing that currently isn't... |
| * |
| * FIXME: should throw an exception if it fails to allocate blocks. |
| */ |
| static int ext2_block_relocator_grab_blocks(struct ext2_fs *fs, struct ext2_block_relocator_state *state) |
| { |
| int i; |
| blk_t ptr; |
| |
| ptr = 0; |
| |
| for (i=0;i<fs->numgroups;i++) |
| if (EXT2_GROUP_FREE_BLOCKS_COUNT(fs->gd[i])) |
| { |
| struct ext2_buffer_head *bh; |
| unsigned int j; |
| int offset; |
| |
| bh = ext2_bread(fs, EXT2_GROUP_BLOCK_BITMAP(fs->gd[i])); |
| offset = i * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb) |
| + EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb); |
| |
| for (j=state->newallocoffset; |
| j<EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb); |
| j++) |
| if (!(bh->data[j>>3] & _bitmap[j&7])) |
| { |
| state->block[ptr++].dest = offset + j; |
| |
| if (ptr == state->usedentries) |
| { |
| ext2_brelse(bh, 0); |
| return 1; |
| } |
| } |
| |
| ext2_brelse(bh, 0); |
| } |
| |
| return 0; |
| } |
| |
| static int ext2_block_relocator_flush(struct ext2_fs *fs, struct ext2_block_relocator_state *state) |
| { |
| int i; |
| |
| if (!state->usedentries) |
| return 1; |
| |
| if (fs->opt_verbose) |
| fputs("ext2_block_relocator_flush\n", stderr); |
| |
| if (fs->opt_debug) |
| { |
| again: |
| |
| for (i=0; (unsigned int) i < state->usedentries-1; i++) |
| if (state->block[i].num >= state->block[i+1].num) |
| { |
| fputs("ext2_block_relocator_flush: " |
| "blocks not in order!\n", stderr); |
| |
| qsort(state->block, |
| state->usedentries, |
| sizeof(struct ext2_block_entry), |
| compare_block_entries); |
| goto again; |
| } |
| } |
| |
| if (!doscan(fs, state)) |
| return 0; |
| |
| if (!ext2_block_relocator_grab_blocks(fs, state)) |
| return 0; |
| |
| if (!ext2_block_relocator_copy(fs, state)) |
| return 0; |
| |
| qsort(state->block, |
| state->usedentries, |
| sizeof(struct ext2_block_entry), |
| compare_block_entries_ind); |
| |
| for (i=3;i>=0;i--) |
| { |
| struct ext2_block_entry *dst; |
| int j; |
| int num; |
| |
| dst = state->start[i].dst; |
| num = state->start[i].num; |
| |
| if (!num) |
| continue; |
| |
| if (fs->opt_verbose) |
| { |
| /* FIXXXME gross hack */ |
| fprintf(stderr, "relocating %s blocks", |
| ((char *[4]){"direct", |
| "singly indirect", |
| "doubly indirect", |
| "triply indirect"})[i]); |
| fflush(stderr); |
| } |
| |
| qsort(dst, |
| num, |
| sizeof(struct ext2_block_entry), |
| compare_block_entries_ref); |
| |
| for (j=0;j<num;j++) |
| if (!ext2_block_relocator_ref(fs, state, &dst[j])) |
| return 0; |
| |
| if (fs->opt_safe) { |
| if (!ext2_sync(fs)) |
| return 0; |
| } |
| |
| if (fs->opt_verbose) |
| fputc('\n', stderr); |
| } |
| |
| state->usedentries = 0; |
| state->resolvedentries = 0; |
| |
| return 1; |
| } |
| |
| static int ext2_block_relocator_mark(struct ext2_fs *fs, struct ext2_block_relocator_state *state, blk_t block) |
| { |
| int i; |
| |
| if (fs->opt_debug) |
| { |
| if (!ext2_get_block_state(fs, block) || |
| !ext2_is_data_block(fs, block)) |
| { |
| ped_exception_throw (PED_EXCEPTION_WARNING, |
| PED_EXCEPTION_IGNORE, |
| _("Block %i shouldn't have been marked " |
| "(%d, %d)!"), block, |
| ext2_get_block_state(fs, block), |
| ext2_is_data_block(fs, block)); |
| } |
| } |
| |
| if (state->usedentries == state->allocentries - 1) |
| if (!ext2_block_relocator_flush(fs, state)) |
| return 0; |
| |
| i = state->usedentries; |
| state->block[i].num = block; |
| state->block[i].dest = 0; |
| state->block[i].refblock = 0; |
| state->block[i].refoffset = 0; |
| |
| state->usedentries++; |
| return 1; |
| } |
| |
| static int ext2_block_relocate_grow(struct ext2_fs *fs, struct ext2_block_relocator_state *state, blk_t newsize) |
| { |
| blk_t newgdblocks; |
| blk_t newitoffset; |
| int i; |
| |
| newgdblocks = ped_div_round_up (newsize |
| - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb), |
| EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb)); |
| newgdblocks = ped_div_round_up (newgdblocks |
| * sizeof(struct ext2_group_desc), |
| fs->blocksize); |
| if (newgdblocks == fs->gdblocks) |
| return 1; |
| |
| newitoffset = newgdblocks + 3; |
| state->newallocoffset = newitoffset + fs->inodeblocks; |
| |
| for (i=0;i<fs->numgroups;i++) |
| { |
| struct ext2_buffer_head *bh; |
| blk_t diff; |
| blk_t j; |
| blk_t start; |
| int sparse; |
| |
| bh = ext2_bread(fs, EXT2_GROUP_BLOCK_BITMAP(fs->gd[i])); |
| start = (i * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb)) |
| + EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb); |
| sparse = ext2_is_group_sparse(fs, i); |
| |
| if (EXT2_GROUP_INODE_TABLE(fs->gd[i]) < start + newitoffset |
| || (sparse && ((EXT2_GROUP_BLOCK_BITMAP(fs->gd[i]) |
| < start + newitoffset - 2) |
| || (EXT2_GROUP_INODE_BITMAP(fs->gd[i]) |
| < start + newitoffset - 1)))) |
| { |
| diff = newitoffset - (EXT2_GROUP_INODE_TABLE(fs->gd[i]) |
| - start); |
| |
| for (j=0;j<diff;j++) |
| { |
| blk_t k; |
| |
| k = EXT2_GROUP_INODE_TABLE(fs->gd[i]) |
| + fs->inodeblocks + j; |
| if (bh->data[k>>3] & _bitmap[k&7]) |
| if (!ext2_block_relocator_mark(fs, |
| state, start + k)) |
| { |
| ext2_brelse(bh, 0); |
| return 0; |
| } |
| } |
| } |
| |
| ext2_brelse(bh, 0); |
| } |
| |
| if (!ext2_block_relocator_flush(fs, state)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int ext2_block_relocate_shrink(struct ext2_fs *fs, struct ext2_block_relocator_state *state, blk_t newsize) |
| { |
| int diff; |
| int i; |
| |
| diff = ped_div_round_up (newsize - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb), |
| EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb)); |
| diff = ped_div_round_up (diff * sizeof(struct ext2_group_desc), |
| fs->blocksize); |
| diff = fs->gdblocks - diff; |
| |
| state->newallocoffset = fs->itoffset + fs->inodeblocks; |
| |
| for (i=0;i<fs->numgroups;i++) |
| { |
| struct ext2_buffer_head *bh; |
| blk_t groupsize; |
| blk_t j; |
| blk_t offset; |
| int sparse; |
| blk_t start; |
| int type; |
| |
| offset = i * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb) |
| + EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb); |
| sparse = ext2_is_group_sparse(fs, i); |
| |
| if (newsize >= offset + EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb)) |
| continue; /* group will survive */ |
| |
| bh = ext2_bread(fs, EXT2_GROUP_BLOCK_BITMAP(fs->gd[i])); |
| |
| if (newsize <= offset) |
| type = 2; /* group is fully chopped off */ |
| else |
| type = 1; /* group is partly chopped off */ |
| |
| if (!sparse && type == 2) |
| { |
| for (j=EXT2_GROUP_INODE_BITMAP(fs->gd[i])+1; |
| j<EXT2_GROUP_INODE_TABLE(fs->gd[i]); |
| j++) |
| { |
| blk_t k; |
| |
| k = j - offset; |
| if (bh->data[k>>3] & _bitmap[k&7]) |
| if (!ext2_block_relocator_mark(fs, state, j)) |
| { |
| ext2_brelse(bh, 0); |
| return 0; |
| } |
| } |
| } |
| |
| start = newsize; |
| if (type == 2) |
| start = EXT2_GROUP_INODE_TABLE(fs->gd[i]) |
| + fs->inodeblocks; |
| |
| start -= offset; |
| |
| groupsize = EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb); |
| if (offset + groupsize > EXT2_SUPER_BLOCKS_COUNT(fs->sb)) |
| groupsize = EXT2_SUPER_BLOCKS_COUNT(fs->sb) - offset; |
| |
| for (j=start;j<groupsize;j++) |
| if (bh->data[j>>3] & _bitmap[j&7]) |
| if (!ext2_block_relocator_mark(fs, state, |
| offset + j)) |
| { |
| ext2_brelse(bh, 0); |
| return 0; |
| } |
| |
| ext2_brelse(bh, 0); |
| } |
| |
| return ext2_block_relocator_flush(fs, state); |
| } |
| |
| int ext2_block_relocate(struct ext2_fs *fs, blk_t newsize) |
| { |
| struct ext2_block_relocator_state state; |
| |
| if (fs->opt_verbose) |
| fputs("relocating blocks....\n", stderr); |
| |
| state.newallocoffset = 0; |
| state.allocentries = (ext2_relocator_pool_size << 10) / |
| sizeof(struct ext2_block_entry); |
| state.usedentries = 0; |
| state.resolvedentries = 0; |
| state.block = (struct ext2_block_entry *)fs->relocator_pool; |
| |
| if (newsize < EXT2_SUPER_BLOCKS_COUNT(fs->sb)) |
| return ext2_block_relocate_shrink(fs, &state, newsize); |
| |
| return ext2_block_relocate_grow(fs, &state, newsize); |
| } |
| |
| #endif /* !DISCOVER_ONLY */ |