| /* |
| ext2_inode_relocator.c -- ext2 inode 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 <sys/stat.h> /* for S_ISDIR */ |
| #include "ext2.h" |
| |
| |
| |
| |
| |
| |
| struct ext2_reference |
| { |
| blk_t block; |
| off_t offset; |
| }; |
| |
| struct ext2_inode_entry |
| { |
| ino_t num; |
| ino_t dest; |
| unsigned numreferences:16; |
| unsigned isdir:1; |
| struct ext2_reference *ref; |
| }; |
| |
| struct ext2_inode_relocator_state |
| { |
| int usedentries; |
| int resolvedentries; |
| struct ext2_inode_entry *inode; |
| struct ext2_reference *last; |
| }; |
| |
| |
| |
| |
| |
| static struct ext2_inode_entry *findit(struct ext2_inode_relocator_state *state, ino_t inode) |
| { |
| int min; |
| int max; |
| struct ext2_inode_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->inode[t].num; |
| |
| t--; |
| if (tval > inode) |
| max = t; |
| |
| t += 2; |
| if (tval < inode) |
| min = t; |
| |
| t--; |
| |
| if (tval != inode) |
| goto repeat; |
| |
| retv = &state->inode[t]; |
| |
| out: |
| return retv; |
| } |
| |
| static int addref(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, ino_t inode, blk_t block, off_t offset) |
| { |
| struct ext2_inode_entry *ent; |
| int i; |
| |
| if ((ent = findit(state, inode)) == NULL) |
| return 1; |
| |
| for (i=0;i<ent->numreferences;i++) |
| if (!ent->ref[i].block) |
| break; |
| |
| if (i == ent->numreferences) |
| { |
| ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, |
| _("Found an inode with a incorrect link count. " |
| "Better go run e2fsck first!")); |
| return 0; |
| } |
| |
| if (i == ent->numreferences - 1) |
| state->resolvedentries++; |
| |
| ent->ref[i].block = block; |
| ent->ref[i].offset = offset; |
| |
| return 1; |
| } |
| |
| static int doblock(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, blk_t blockno) |
| { |
| struct ext2_buffer_head *bh; |
| off_t offset; |
| |
| bh = ext2_bread(fs, blockno); |
| if (!bh) |
| return 0; |
| |
| offset = 0; |
| do |
| { |
| struct ext2_dir_entry_2 *ptr; |
| |
| ptr = (struct ext2_dir_entry_2 *)(bh->data + offset); |
| |
| if (ptr->name_len) |
| if (!addref(fs, state, EXT2_DIRENT_INODE(*ptr), blockno, |
| offset)) |
| return 0; |
| |
| PED_ASSERT (ptr->rec_len > 0, return 0); |
| offset += EXT2_DIRENT_REC_LEN (*ptr); |
| } while (offset < fs->blocksize); |
| |
| ext2_brelse(bh, 0); |
| return 1; |
| } |
| |
| static int doindblock(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, blk_t blockno) |
| { |
| struct ext2_buffer_head *bh; |
| blk_t blk; |
| int i; |
| |
| bh = ext2_bread(fs, blockno); |
| |
| for (i=0;i<(fs->blocksize>>2);i++) |
| if ((blk = PED_LE32_TO_CPU(((uint32_t *)bh->data)[i])) != 0) |
| if (!doblock(fs, state, blk)) |
| return 0; |
| |
| ext2_brelse(bh, 0); |
| return 1; |
| } |
| |
| static int dodindblock(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, blk_t blockno) |
| { |
| struct ext2_buffer_head *bh; |
| blk_t blk; |
| int i; |
| |
| bh = ext2_bread(fs, blockno); |
| if (!bh) |
| return 0; |
| |
| for (i=0;i<(fs->blocksize>>2);i++) |
| if ((blk = PED_LE32_TO_CPU(((uint32_t *)bh->data)[i])) != 0) |
| if (!doindblock(fs, state, blk)) |
| return 0; |
| |
| ext2_brelse(bh, 0); |
| return 1; |
| } |
| |
| static int dotindblock(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, blk_t blockno) |
| { |
| struct ext2_buffer_head *bh; |
| blk_t blk; |
| int i; |
| |
| bh = ext2_bread(fs, blockno); |
| if (!bh) |
| return 0; |
| |
| for (i=0;i<(fs->blocksize>>2);i++) |
| if ((blk = PED_LE32_TO_CPU(((uint32_t *)bh->data)[i])) != 0) |
| if (!dodindblock(fs, state, blk)) |
| return 0; |
| |
| ext2_brelse(bh, 0); |
| return 1; |
| } |
| |
| static int doinode(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, ino_t inode) |
| { |
| struct ext2_inode buf; |
| int i; |
| |
| if (!ext2_read_inode(fs, inode, &buf)) |
| return 0; |
| if (S_ISDIR(EXT2_INODE_MODE(buf))) |
| { |
| blk_t blk; |
| |
| for (i=0;i<EXT2_NDIR_BLOCKS;i++) |
| if ((blk = EXT2_INODE_BLOCK(buf, i)) != 0) |
| if (!doblock(fs, state, blk)) |
| return 0; |
| |
| if ((blk = EXT2_INODE_BLOCK(buf, EXT2_IND_BLOCK)) != 0) |
| if (!doindblock(fs, state, blk)) |
| return 0; |
| |
| if ((blk = EXT2_INODE_BLOCK(buf, EXT2_DIND_BLOCK)) != 0) |
| if (!dodindblock(fs, state, blk)) |
| return 0; |
| |
| if ((blk = EXT2_INODE_BLOCK(buf, EXT2_TIND_BLOCK)) != 0) |
| if (!dotindblock(fs, state, blk)) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int doscangroup(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, int group) |
| { |
| struct ext2_buffer_head *bh; |
| unsigned int i; |
| int offset; |
| |
| if (fs->opt_verbose) |
| fprintf(stderr, " scanning group %i.... ", group); |
| |
| bh = ext2_bread(fs, EXT2_GROUP_INODE_BITMAP(fs->gd[group])); |
| offset = group * EXT2_SUPER_INODES_PER_GROUP(fs->sb) + 1; |
| |
| for (i=0;i<EXT2_SUPER_INODES_PER_GROUP(fs->sb);i++) |
| if (bh->data[i>>3] & _bitmap[i&7]) |
| { |
| if (!doinode(fs, state, offset + i)) |
| { |
| ext2_brelse(bh, 0); |
| return 0; |
| } |
| |
| if (state->resolvedentries == state->usedentries) |
| break; |
| } |
| |
| ext2_brelse(bh, 0); |
| |
| if (fs->opt_verbose) |
| fprintf(stderr, |
| "%i/%i inodes resolved\r", |
| state->resolvedentries, |
| state->usedentries); |
| |
| return 1; |
| } |
| |
| /* basically: this builds a dependency graph of the inodes in the entire file |
| * system. inodes are only referenced by the directory tree (or the magic |
| * ones implicitly, like the bad blocks inode), so we just walk the directory |
| * tree adding references. |
| */ |
| static int doscan(struct ext2_fs *fs, struct ext2_inode_relocator_state *state) |
| { |
| int i; |
| |
| /* while the journal will usually be inode 8 (and therefore will never |
| * need to be moved), we don't have any guarantee (grrr). So, we |
| * need to be prepared to move it... (and update the reference in the |
| * super block) |
| */ |
| if (fs->has_internal_journal) |
| addref(fs, state, EXT2_SUPER_JOURNAL_INUM(fs->sb), |
| 1, offsetof(struct ext2_super_block, s_journal_inum)); |
| |
| if (!doscangroup(fs, state, 0)) |
| return 0; |
| |
| if (state->resolvedentries != state->usedentries) |
| for (i=fs->numgroups-1;i>0;i--) |
| { |
| if (!doscangroup(fs, state, i)) |
| return 0; |
| |
| if (state->resolvedentries == state->usedentries) |
| break; |
| } |
| |
| if (fs->opt_verbose) |
| fputc ('\n', stderr); |
| |
| return 1; |
| } |
| |
| |
| |
| |
| |
| |
| |
| static int ext2_inode_relocator_copy(struct ext2_fs *fs, struct ext2_inode_relocator_state *state) |
| { |
| int i; |
| |
| for (i=0;i<state->usedentries;i++) |
| { |
| struct ext2_inode buf; |
| struct ext2_inode_entry *entry; |
| |
| entry = &state->inode[i]; |
| |
| if (fs->opt_debug) |
| if (!ext2_get_inode_state(fs, entry->num) || |
| ext2_get_inode_state(fs, entry->dest)) |
| fputs ("inodebitmaperror\n", stderr); |
| |
| if (!ext2_read_inode(fs, entry->num, &buf)) |
| return 0; |
| if (!ext2_write_inode(fs, entry->dest, &buf)) |
| return 0; |
| |
| entry->isdir = S_ISDIR(EXT2_INODE_MODE(buf))?1:0; |
| } |
| |
| if (fs->opt_safe) |
| if (!ext2_sync(fs)) |
| return 0; |
| return 1; |
| } |
| |
| static int ext2_inode_relocator_finish(struct ext2_fs *fs, struct ext2_inode_relocator_state *state) |
| { |
| int i; |
| |
| for (i=0;i<state->usedentries;i++) |
| { |
| struct ext2_inode_entry *entry; |
| |
| entry = &state->inode[i]; |
| ext2_set_inode_state(fs, entry->dest, 1, 1); |
| ext2_set_inode_state(fs, entry->num, 0, 1); |
| ext2_zero_inode(fs, entry->num); |
| } |
| |
| if (fs->opt_safe) |
| if (!ext2_sync(fs)) |
| return 0; |
| return 1; |
| } |
| |
| static int ext2_inode_relocator_ref(struct ext2_fs *fs, struct ext2_inode_relocator_state *state) |
| { |
| int i; |
| static int numerrors = 0; |
| |
| for (i=0;i<state->usedentries;i++) |
| { |
| struct ext2_inode_entry *entry; |
| int j; |
| uint32_t t; |
| |
| entry = &state->inode[i]; |
| t = entry->dest; |
| |
| for (j=0;j<entry->numreferences;j++) |
| { |
| struct ext2_buffer_head *bh; |
| |
| bh = ext2_bread(fs, entry->ref[j].block); |
| if (!bh) |
| return 0; |
| |
| if (fs->opt_debug) |
| { |
| if (PED_LE32_TO_CPU((*((uint32_t *)(bh->data + entry->ref[j].offset)))) != entry->num) |
| { |
| fprintf(stderr, |
| "inode %li ref error! (->%li, [%i]={%i, %i})\n", |
| (long) entry->num, |
| (long) entry->dest, |
| j, |
| entry->ref[j].block, |
| (int) entry->ref[j].offset); |
| ext2_brelse(bh, 0); |
| |
| if (numerrors++ < 4) |
| continue; |
| |
| fputs ("all is not well!\n", stderr); |
| return 0; |
| } |
| } |
| |
| *((uint32_t *)(bh->data + entry->ref[j].offset)) |
| = PED_CPU_TO_LE32(t); |
| bh->dirty = 1; |
| |
| ext2_brelse(bh, 0); |
| } |
| |
| if (entry->isdir) |
| { |
| int oldgroup; |
| int newgroup; |
| |
| oldgroup = (entry->num - 1) |
| / EXT2_SUPER_INODES_PER_GROUP(fs->sb); |
| newgroup = (entry->dest - 1) |
| / EXT2_SUPER_INODES_PER_GROUP(fs->sb); |
| |
| fs->gd[oldgroup].bg_used_dirs_count = PED_CPU_TO_LE16 ( |
| EXT2_GROUP_USED_DIRS_COUNT(fs->gd[oldgroup]) |
| - 1); |
| fs->gd[newgroup].bg_used_dirs_count = PED_CPU_TO_LE16 ( |
| EXT2_GROUP_USED_DIRS_COUNT(fs->gd[newgroup]) |
| + 1); |
| |
| fs->metadirty = EXT2_META_GD; |
| } |
| } |
| |
| if (fs->opt_safe) |
| if (!ext2_sync(fs)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int ext2_inode_relocator_grab_inodes(struct ext2_fs *fs, struct ext2_inode_relocator_state *state) |
| { |
| int i; |
| int ptr; |
| |
| ptr = 0; |
| |
| for (i=0;i<fs->numgroups;i++) |
| if (EXT2_GROUP_FREE_INODES_COUNT(fs->gd[i])) |
| { |
| struct ext2_buffer_head *bh; |
| unsigned int j; |
| int offset; |
| |
| 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; |
| |
| j = i ? 0 : 13; |
| for (;j<EXT2_SUPER_INODES_PER_GROUP(fs->sb);j++) |
| if (!(bh->data[j>>3] & _bitmap[j&7])) |
| { |
| state->inode[ptr++].dest = offset + j; |
| |
| if (ptr == state->usedentries) |
| { |
| ext2_brelse(bh, 0); |
| return 1; |
| } |
| } |
| |
| ext2_brelse(bh, 0); |
| } |
| |
| ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, |
| _("Not enough free inodes!")); |
| return 0; |
| } |
| |
| static int ext2_inode_relocator_flush(struct ext2_fs *fs, struct ext2_inode_relocator_state *state) |
| { |
| if (!state->usedentries) |
| return 1; |
| |
| if (!doscan(fs, state)) |
| return 0; |
| |
| if (!ext2_inode_relocator_grab_inodes(fs, state)) |
| return 0; |
| |
| if (!ext2_inode_relocator_copy(fs, state)) |
| return 0; |
| |
| if (!ext2_inode_relocator_ref(fs, state)) |
| return 0; |
| |
| if (!ext2_inode_relocator_finish(fs, state)) |
| return 0; |
| |
| state->usedentries = 0; |
| state->resolvedentries = 0; |
| state->last = (struct ext2_reference *)fs->relocator_pool_end; |
| |
| if (fs->opt_safe) |
| if (!ext2_sync(fs)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int ext2_inode_relocator_mark(struct ext2_fs *fs, struct ext2_inode_relocator_state *state, ino_t inode) |
| { |
| struct ext2_inode buf; |
| struct ext2_inode_entry *ent; |
| int i; |
| |
| if (!ext2_read_inode(fs, inode, &buf)) |
| return 0; |
| |
| { |
| register void *adv; |
| register void *rec; |
| |
| adv = state->inode + state->usedentries + 1; |
| rec = state->last - EXT2_INODE_LINKS_COUNT(buf); |
| |
| if (adv >= rec) |
| ext2_inode_relocator_flush(fs, state); |
| } |
| |
| state->last -= EXT2_INODE_LINKS_COUNT(buf); |
| |
| ent = &state->inode[state->usedentries]; |
| ent->num = inode; |
| ent->dest = 0; |
| ent->numreferences = EXT2_INODE_LINKS_COUNT(buf); |
| ent->ref = state->last; |
| |
| for (i=0;i<ent->numreferences;i++) |
| { |
| ent->ref[i].block = 0; |
| ent->ref[i].offset = 0; |
| } |
| |
| state->usedentries++; |
| |
| return 1; |
| } |
| |
| |
| int ext2_inode_relocate(struct ext2_fs *fs, int newgroups) |
| { |
| int i; |
| struct ext2_inode_relocator_state state; |
| |
| if (fs->opt_verbose) |
| fputs ("ext2_inode_relocate\n", stderr); |
| |
| state.usedentries = 0; |
| state.resolvedentries = 0; |
| state.inode = (struct ext2_inode_entry *)fs->relocator_pool; |
| state.last = (struct ext2_reference *)fs->relocator_pool_end; |
| |
| for (i=newgroups;i<fs->numgroups;i++) |
| { |
| struct ext2_buffer_head *bh; |
| unsigned int j; |
| int offset; |
| |
| 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]) |
| ext2_inode_relocator_mark(fs, &state, |
| offset + j); |
| |
| ext2_brelse(bh, 0); |
| } |
| |
| if (!ext2_inode_relocator_flush(fs, &state)) |
| return 0; |
| |
| return 1; |
| } |
| #endif /* !DISCOVER_ONLY */ |
| |