/** * A tool for ext2/ext3/ext4 filesystems that allows to change inode count * without recreating it. * * TODO bigalloc compatibility * TODO support undo, but not by undo_io_manager because it is VERY slow * TODO check the bad block inode before block moving * TODO check if the block mover does block_alloc_stats * TODO write some tests: for inode moving (image with many files), * for block moving, including extent blocks (one sparse file with many extents), * for calculating block group statistics (block moving between different groups) * * The theory isn't that hard: * 1) If shrinking - move inodes away from the end of each block group inode table * 1.1) move each inode to the new place, mark new place as occupied, unmark old one * 1.2) remember the old->new inode number mapping * 2) If growing - move data away from extra blocks needed by growing inode tables: * 2.1) Create a map of blocks that we want to free * 2.2) Iterate through all inodes and move remembered blocks. * It involves overwriting the whole file extent tree or block mapping... * If some of these blocks are in the bad block inode, we should either * abort the reallocation process, or move inode tables to another location * in a block group, possibly first defragmenting it... :-( * 3) Change all inode numbers in directory entries according to mappings from (1.2), * and then using a formula: new_num = 1 + ((old_num-1)/old_i_per_g)*new_i_per_g + ((old_num-1) % old_i_per_g) * 4) Move parts of inode tables so they are consecutive again if flex_bg feature is active * 5) Mark/unmark extra blocks for inode tables * 6) Change block group descriptors: bg_inode_table, bg_free_inodes_count, * bg_free_blocks_count, bg_inode_bitmap_csum, bg_itable_unused * 7) Change superblock: s_inodes_count, s_free_blocks_count, * s_free_inodes_count, s_inodes_per_group * * This is a highly destructive process and WILL leave a corrupted FS if interrupted. * So we should provide a rollback method. undo_io_manager is very slow, so maybe the * original idea should be implemented: create the "patch" i/o manager that writes all * changed blocks to a separate file and only then applies it. */ #include #include #include #include #include #include #include #include #include #include #include #define _(a) (a) #define min(a,b) ((a)<(b)?(a):(b)) errcode_t ext2fs_move_blocks(ext2_filsys fs, ext2fs_block_bitmap reserve, ext2fs_block_bitmap alloc_map, int flags); // "local data" for the inode reallocation process typedef struct { ext2_filsys fs; int fs_fd; char *device_name, *io_options; __u32 ig_old, ig_new; // old and new inodes-per-group count __u32 ibg_old, ibg_new; // old and new inode_blocks-per-group count __u32 new_inode_count; // (old->new) inode number map ext2_ino_t *inode_map; __u32 inode_map_size, inode_map_alloc; } realloc_data; // Utility functions for (old -> new) inode number map void realloc_add_inode_map(realloc_data *rd, ext2_ino_t old, ext2_ino_t new) { if (!old || !new) { return; } if (2*rd->inode_map_size >= rd->inode_map_alloc) { rd->inode_map_alloc += 1024; rd->inode_map = realloc(rd->inode_map, sizeof(ext2_ino_t) * rd->inode_map_alloc); } rd->inode_map[rd->inode_map_size*2] = old; rd->inode_map[rd->inode_map_size*2+1] = new; rd->inode_map_size++; } int realloc_compare_inode_map_callback(const void *a, const void *b) { return *((ext2_ino_t*)a) - *((ext2_ino_t*)b); } void realloc_sort_inode_map(realloc_data *rd) { if (!rd->inode_map) { return; } qsort(rd->inode_map, rd->inode_map_size, sizeof(ext2_ino_t)*2, realloc_compare_inode_map_callback); } ext2_ino_t realloc_search_inode_map(realloc_data *rd, ext2_ino_t old) { __u32 start = 0, end = rd->inode_map_size, cur; ext2_ino_t cur_ino; if (!rd->inode_map) { return 0; } while (end-start > 1) { cur = (start+end)>>1; cur_ino = rd->inode_map[cur<<1]; if (cur_ino < old) { start = cur+1; } else if (cur_ino > old) { end = cur; } else { return rd->inode_map[(cur<<1)+1]; } } if (rd->inode_map[start<<1] == old) { return rd->inode_map[(start<<1)+1]; } return 0; } // Copy-paste from e2fsprogs: lib/ext2fs/alloc.c /* * Check for uninit block bitmaps and deal with them appropriately */ static void check_block_uninit(ext2_filsys fs, ext2fs_block_bitmap map, dgrp_t group) { blk_t i; blk64_t blk, super_blk, old_desc_blk, new_desc_blk; int old_desc_blocks; if (!(EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) || !(ext2fs_bg_flags_test(fs, group, EXT2_BG_BLOCK_UNINIT))) return; blk = (group * fs->super->s_blocks_per_group) + fs->super->s_first_data_block; ext2fs_super_and_bgd_loc2(fs, group, &super_blk, &old_desc_blk, &new_desc_blk, 0); if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) old_desc_blocks = fs->super->s_first_meta_bg; else old_desc_blocks = fs->desc_blocks + fs->super->s_reserved_gdt_blocks; for (i=0; i < fs->super->s_blocks_per_group; i++, blk++) ext2fs_fast_unmark_block_bitmap2(map, blk); blk = (group * fs->super->s_blocks_per_group) + fs->super->s_first_data_block; for (i=0; i < fs->super->s_blocks_per_group; i++, blk++) { if ((blk == super_blk) || (old_desc_blk && old_desc_blocks && (blk >= old_desc_blk) && (blk < old_desc_blk + old_desc_blocks)) || (new_desc_blk && (blk == new_desc_blk)) || (blk == ext2fs_block_bitmap_loc(fs, group)) || (blk == ext2fs_inode_bitmap_loc(fs, group)) || (blk >= ext2fs_inode_table_loc(fs, group) && (blk < ext2fs_inode_table_loc(fs, group) + fs->inode_blocks_per_group))) ext2fs_fast_mark_block_bitmap2(map, blk); } ext2fs_bg_flags_clear(fs, group, EXT2_BG_BLOCK_UNINIT); ext2fs_group_desc_csum_set(fs, group); ext2fs_mark_super_dirty(fs); ext2fs_mark_bb_dirty(fs); } /* * Check for uninit inode bitmaps and deal with them appropriately */ static void check_inode_uninit(ext2_filsys fs, ext2fs_inode_bitmap map, dgrp_t group) { ext2_ino_t i, ino; if (!(EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) || !(ext2fs_bg_flags_test(fs, group, EXT2_BG_INODE_UNINIT))) return; ino = (group * fs->super->s_inodes_per_group) + 1; for (i=0; i < fs->super->s_inodes_per_group; i++, ino++) ext2fs_fast_unmark_inode_bitmap2(map, ino); ext2fs_bg_flags_clear(fs, group, EXT2_BG_INODE_UNINIT); ext2fs_group_desc_csum_set(fs, group); ext2fs_mark_ib_dirty(fs); ext2fs_mark_super_dirty(fs); check_block_uninit(fs, fs->block_map, group); } /** * Move inodes from the end of each block group inode table * so the tables can be shrinked */ int shrink_move_inodes(realloc_data *rd) { int retval = 0, inode_size = EXT2_INODE_SIZE(rd->fs->super); __u32 group, i; __u32 new_group; ext2_ino_t ino, new_ino; struct ext2_inode *inode = NULL; ext2fs_read_inode_bitmap(rd->fs); if (retval) { return retval; } retval = ext2fs_get_mem(inode_size, &inode); if (retval) { return retval; } for (group = 0; group < rd->fs->group_desc_count; group++) { for (i = rd->ig_new; i < rd->ig_old; i++) { ino = 1 + group*rd->ig_old + i; if (ext2fs_test_inode_bitmap2(rd->fs->inode_map, ino)) { // Inode is occupied and should be moved new_group = group; do { retval = ext2fs_find_first_zero_inode_bitmap2(rd->fs->inode_map, 1 + new_group*rd->ig_old, new_group*rd->ig_old+rd->ig_new, &new_ino); if (!retval) { break; } new_group = (new_group+1) % rd->fs->group_desc_count; } while (new_group != group); if (retval) { // No space to move this inode goto out; } // Copy inode to the new place check_inode_uninit(rd->fs, rd->fs->inode_map, new_group); retval = ext2fs_read_inode_full(rd->fs, ino, inode, inode_size); if (retval) { goto out; } retval = ext2fs_write_inode_full(rd->fs, new_ino, inode, inode_size); if (retval) { goto out; } ext2fs_inode_alloc_stats2(rd->fs, new_ino, 1, inode->i_mode & S_IFDIR); ext2fs_inode_alloc_stats2(rd->fs, ino, -1, inode->i_mode & S_IFDIR); // Remember mapping realloc_add_inode_map(rd, ino, new_ino); } } } if (rd->inode_map_size) { ext2fs_mark_ib_dirty(rd->fs); } out: if (inode) { ext2fs_free_mem(&inode); } return retval; } /** * Move data blocks from after the end of each block group inode table * so the tables can be grown */ int extend_move_blocks(realloc_data *rd) { ext2fs_block_bitmap reserve_map; blk64_t it_start, blk_diff, b_per_g; dgrp_t flex_grp, n_grp, flex_count; int retval, flexbg_size; if (rd->ibg_new == rd->ibg_old) { return 0; } blk_diff = rd->ibg_new-rd->ibg_old; b_per_g = EXT2_BLOCKS_PER_GROUP(rd->fs->super); retval = ext2fs_allocate_block_bitmap(rd->fs, "reserved block map", &reserve_map); if (retval) { return retval; } if (!rd->fs->block_map) { ext2fs_read_block_bitmap(rd->fs); } // Mark reserved blocks (those we want to free) if (EXT2_HAS_INCOMPAT_FEATURE(rd->fs->super, EXT4_FEATURE_INCOMPAT_FLEX_BG) && rd->fs->super->s_log_groups_per_flex) { flexbg_size = 1 << rd->fs->super->s_log_groups_per_flex; } else { flexbg_size = 1; } flex_count = (rd->fs->group_desc_count + flexbg_size - 1) / flexbg_size; for (flex_grp = 0; flex_grp < flex_count; flex_grp++) { n_grp = flexbg_size; if (flex_grp*flexbg_size+n_grp > rd->fs->group_desc_count) { n_grp = rd->fs->group_desc_count-flex_grp*flexbg_size; } it_start = ext2fs_inode_table_loc(rd->fs, flex_grp*flexbg_size); // Check group boundaries (just in case) if ((it_start + rd->ibg_new*n_grp - 1) / b_per_g != (it_start + rd->ibg_old*n_grp - 1) / b_per_g) { retval = ENOSPC; goto out; } it_start += rd->ibg_old*n_grp; ext2fs_mark_block_bitmap_range2(reserve_map, it_start, blk_diff*n_grp); } retval = ext2fs_move_blocks(rd->fs, reserve_map, rd->fs->block_map, 0); ext2fs_mark_bb_dirty(rd->fs); ext2fs_flush(rd->fs); out: ext2fs_free_block_bitmap(reserve_map); return retval; } static int change_inode_numbers_callback(ext2_ino_t dir, int entry, struct ext2_dir_entry *dirent, int offset, int blocksize, char *buf, void *priv_data) { realloc_data *rd = priv_data; ext2_ino_t new_ino = realloc_search_inode_map(rd, dirent->inode); if (!new_ino) { new_ino = dirent->inode; } new_ino = 1 + (new_ino-1)/rd->ig_old*rd->ig_new + (new_ino-1)%rd->ig_old; if (new_ino != dirent->inode) { dirent->inode = new_ino; return DIRENT_CHANGED; } return 0; } /** * Change inode numbers in all directory entries */ int change_inode_numbers(realloc_data *rd) { ext2_ino_t ino; realloc_sort_inode_map(rd); for (ino = 1; ino <= rd->fs->super->s_inodes_count; ino++) { ext2fs_dir_iterate2(rd->fs, ino, 0, 0, change_inode_numbers_callback, rd); } return 0; } /** * 1) Move inode tables so they are consecutive again if flex_bg is enabled * 2) Mark/unmark extra inode table blocks * 3) Adjust superblock and block group descriptors */ int change_super_and_bgd(realloc_data *rd) { blk64_t it_start, blk; dgrp_t grp, flex_grp, flex_count; __u32 unus, used_ibg; int flexbg_size, n_grp, i, retval = 0; int has_gdt_csum = EXT2_HAS_RO_COMPAT_FEATURE(rd->fs->super, EXT4_FEATURE_RO_COMPAT_GDT_CSUM); void *buf = NULL; ext2fs_flush(rd->fs); if (!rd->fs->block_map) { ext2fs_read_block_bitmap(rd->fs); } if (EXT2_HAS_INCOMPAT_FEATURE(rd->fs->super, EXT4_FEATURE_INCOMPAT_FLEX_BG) && rd->fs->super->s_log_groups_per_flex) { flexbg_size = 1 << rd->fs->super->s_log_groups_per_flex; } else { flexbg_size = 1; } flex_count = (rd->fs->group_desc_count + flexbg_size - 1) / flexbg_size; retval = ext2fs_get_mem(EXT2_BLOCK_SIZE(rd->fs->super) * rd->ibg_new * flexbg_size, &buf); if (retval) { goto out; } for (flex_grp = 0; flex_grp < flex_count; flex_grp++) { n_grp = flexbg_size; if (flex_grp*flexbg_size+n_grp > rd->fs->group_desc_count) { n_grp = rd->fs->group_desc_count-flex_grp*flexbg_size; } it_start = ext2fs_inode_table_loc(rd->fs, flex_grp*flexbg_size); if (rd->ibg_new != rd->ibg_old) { memset(buf, 0, EXT2_BLOCK_SIZE(rd->fs->super) * rd->ibg_new * n_grp); // Read inode table(s) while skipping unitialized inode table parts for (grp = flex_grp*flexbg_size, i = 0; i < n_grp; grp++, i++) { used_ibg = rd->ibg_old; if (has_gdt_csum) { if (ext2fs_bg_flags_test(rd->fs, grp, EXT2_BG_INODE_UNINIT)) { used_ibg = 0; } else { used_ibg = (rd->ig_old - ext2fs_bg_itable_unused(rd->fs, grp)); used_ibg = (used_ibg * EXT2_INODE_SIZE(rd->fs->super)+EXT2_BLOCK_SIZE(rd->fs->super)-1)/EXT2_BLOCK_SIZE(rd->fs->super); } } if (used_ibg > 0) { blk = ext2fs_inode_table_loc(rd->fs, grp); retval = io_channel_read_blk64(rd->fs->io, blk, min(used_ibg, rd->ibg_new), buf + i*rd->ibg_new*EXT2_BLOCK_SIZE(rd->fs->super)); if (retval) { goto out; } } } // Write inode table(s) to the new place retval = io_channel_write_blk64(rd->fs->io, it_start, rd->ibg_new * n_grp, buf); if (retval) { // Exiting with badly corrupted filesystem :-( printf("Error moving inode tables for %u groups, starting from %u\n", n_grp, flex_grp*flexbg_size); goto out; } // Mark/unmark extra inode table blocks if (rd->ibg_new < rd->ibg_old) { ext2fs_unmark_block_bitmap_range2(rd->fs->block_map, it_start + rd->ibg_new*n_grp, (rd->ibg_old-rd->ibg_new)*n_grp); } else { ext2fs_mark_block_bitmap_range2(rd->fs->block_map, it_start + rd->ibg_old*n_grp, (rd->ibg_new-rd->ibg_old)*n_grp); } } ext2fs_bg_free_blocks_count_set(rd->fs, flex_grp*flexbg_size, ext2fs_bg_free_blocks_count(rd->fs, flex_grp*flexbg_size) - (rd->ibg_new - rd->ibg_old)*n_grp); // Change inode table locations and free inode counts for (grp = flex_grp*flexbg_size, i = 0; i < n_grp; grp++, i++) { blk = it_start + rd->ibg_new*i; ext2fs_inode_table_loc_set(rd->fs, grp, blk); ext2fs_bg_free_inodes_count_set(rd->fs, grp, ext2fs_bg_free_inodes_count(rd->fs, grp) + rd->ig_new - rd->ig_old); if (has_gdt_csum) { unus = ext2fs_bg_itable_unused(rd->fs, grp); if (rd->ig_new > rd->ig_old || unus >= rd->ig_old - rd->ig_new) { unus += rd->ig_new - rd->ig_old; } else { unus = 0; } ext2fs_bg_itable_unused_set(rd->fs, grp, unus); ext2fs_bg_flags_clear(rd->fs, grp, EXT2_BG_BLOCK_UNINIT); ext2fs_group_desc_csum_set(rd->fs, grp); } } } // Bitmaps never need to be moved because a single bitmap is always a single FS block ext2fs_mark_bb_dirty(rd->fs); retval = rd->fs->write_bitmaps(rd->fs); if (retval) { goto out; } rd->fs->write_bitmaps = NULL; // Explicitly set 'overwrite backup superblocks' flag rd->fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; ext2fs_free_blocks_count_add(rd->fs->super, rd->fs->group_desc_count * (rd->ibg_old - rd->ibg_new)); rd->fs->super->s_free_inodes_count += rd->fs->group_desc_count * (rd->ig_new - rd->ig_old); rd->fs->super->s_inodes_per_group = rd->ig_new; rd->fs->super->s_inodes_count = rd->fs->group_desc_count * rd->ig_new; ext2fs_mark_super_dirty(rd->fs); if (rd->ig_new > rd->ig_old) { // Mark newly allocated inodes as free in the bitmap __u32 ino; ext2fs_read_inode_bitmap(rd->fs); for (grp = 0; grp < rd->fs->group_desc_count; grp++) { for (ino = rd->ig_old; ino < rd->ig_new; ino++) { ext2fs_unmark_inode_bitmap2(rd->fs->inode_map, 1 + ino + grp*rd->ig_new); } } ext2fs_mark_ib_dirty(rd->fs); } out: if (buf) { ext2fs_free_mem(&buf); } return retval; } /** * Main function: change inode number of a filesystem! */ int do_realloc(realloc_data *rd) { __u32 ig_round; int retval; rd->ig_old = EXT2_INODES_PER_GROUP(rd->fs->super); rd->ig_new = rd->new_inode_count / rd->fs->group_desc_count; // inodes-per-group must be a multiple of 8 so each byte of inode bitmap is filled rd->ig_new &= ~7; if (rd->ig_new < 16) { printf("Too small number of inodes requested (%u), min inodes per group = 16\n", rd->ig_new); return ENOENT; } rd->ibg_old = rd->fs->inode_blocks_per_group; rd->ibg_new = (rd->ig_new * EXT2_INODE_SIZE(rd->fs->super) + EXT2_BLOCK_SIZE(rd->fs->super) - 1) / EXT2_BLOCK_SIZE(rd->fs->super); if (rd->new_inode_count != rd->ig_new * rd->fs->group_desc_count) { printf("Inode count %u rounded down to %u = (%u inodes per group) * (%u block groups)\n", rd->new_inode_count, rd->ig_new * rd->fs->group_desc_count, rd->ig_new, rd->fs->group_desc_count); } rd->new_inode_count = rd->ig_new * rd->fs->group_desc_count; ig_round = rd->ibg_new * EXT2_BLOCK_SIZE(rd->fs->super) / EXT2_INODE_SIZE(rd->fs->super); if (rd->ig_new != ig_round) { printf("Inode count %u is not optimal because %u inodes per group is not a multiple of %u" " - there will be wasted space in inode tables. Optimal inode count would be %u.\n", rd->new_inode_count, rd->ig_new, EXT2_BLOCK_SIZE(rd->fs->super) / EXT2_INODE_SIZE(rd->fs->super), ig_round); } if (rd->ig_new < rd->ig_old) { if (rd->new_inode_count < rd->fs->super->s_inodes_count - rd->fs->super->s_free_inodes_count) { printf("Too small number of inodes requested, existing inodes (%u) won't fit\n", rd->fs->super->s_inodes_count - rd->fs->super->s_free_inodes_count); return ENOENT; } printf("Phase 1: Moving inodes out of the way\n"); retval = shrink_move_inodes(rd); if (retval) { return retval; } } else if (rd->ig_new > rd->ig_old) { blk64_t required_blocks = (rd->ibg_new - rd->ibg_old) * rd->fs->group_desc_count; if (required_blocks > ext2fs_free_blocks_count(rd->fs->super)) { printf("Requested number of inodes is too big, it requires at least %llu free blocks, " "and there are only %llu free blocks available\n", required_blocks, ext2fs_free_blocks_count(rd->fs->super)); return ENOENT; } printf("Phase 1: Moving data blocks out of the way\n"); retval = extend_move_blocks(rd); if (retval) { return retval; } } else { printf("The requested number of inodes is equal to current\n"); return 0; } printf("Phase 2: Changing all inode numbers\n"); retval = change_inode_numbers(rd); if (retval) { return retval; } printf("Phase 3: Adjusting superblock and block group descriptors\n"); retval = change_super_and_bgd(rd); if (retval) { return retval; } return 0; } __u32 atou(char *s) { __u32 x = 0; if (s[0] == '0') { if (s[1] == 'x' || s[1] == 'X') { sscanf(s+2, "%x", &x); } else { sscanf(s+1, "%o", &x); } } else { sscanf(s, "%u", &x); } return x; } static char *basename(char *name) { char *n1 = rindex(name, '/'), *n2 = rindex(name, '\\'); if (!n1 && !n2) { return name; } else if (n1 < n2) { return n1+1; } else { return n2+1; } } const char *program_name = "realloc-inodes"; static int setup_tdb(char *name, io_manager *io_ptr) { errcode_t retval = 0; const char *tdb_dir; char *tdb_file; char *dev_name; dev_name = basename(name); tdb_dir = getenv("E2FSPROGS_UNDO_DIR"); if (!tdb_dir) { tdb_dir = "/var/lib/e2fsprogs"; } if (!strcmp(tdb_dir, "none") || (tdb_dir[0] == 0)) { fprintf(stderr, "Can't save undo file to %s; specify different undo directory with E2FSPROGS_UNDO_DIR env variable\n", tdb_dir); return ENOENT; } if ((retval = access(tdb_dir, W_OK))) { fprintf(stderr, "Can't save undo file to %s; specify different undo directory with E2FSPROGS_UNDO_DIR env variable\n", tdb_dir); return EACCES; } tdb_file = malloc(strlen(tdb_dir) + 2 + 14 + strlen(dev_name) + 7 + 1); if (!tdb_file) { fprintf(stderr, "Bad alloc\n"); return ENOMEM; } sprintf(tdb_file, "%s/realloc-inodes-%s.e2undo", tdb_dir, dev_name); if (!access(tdb_file, F_OK) && (unlink(tdb_file) < 0)) { retval = errno; com_err(program_name, retval, _("while trying to delete %s"), tdb_file); free(tdb_file); return retval; } set_undo_io_backing_manager(*io_ptr); *io_ptr = undo_io_manager; set_undo_io_backup_file(tdb_file); printf(_("To undo the %s operation please run the command\n e2undo %s %s\n\n"), program_name, tdb_file, name); free(tdb_file); return retval; } int main(int narg, char **args) { realloc_data rd = { 0 }; int optind, retval, io_flags = 0, force = 0; io_manager io_ptr = unix_io_manager; struct stat st_buf; if (narg < 3) { printf("USAGE: ./realloc-inodes \n"); return 0; } optind = 1; rd.device_name = args[optind++]; rd.new_inode_count = atou(args[optind++]); add_error_table(&et_ext2_error_table); // Open FS rd.fs_fd = ext2fs_open_file(rd.device_name, O_RDWR, 0); if (rd.fs_fd < 0) { com_err(program_name, errno, _("while opening %s"), rd.device_name); exit(1); } retval = fstat(rd.fs_fd, &st_buf); if (retval < 0) { com_err(program_name, errno, _("while getting stat information for %s"), rd.device_name); exit(1); } if (!S_ISREG(st_buf.st_mode)) { close(rd.fs_fd); rd.fs_fd = -1; } rd.io_options = strchr(rd.device_name, '?'); if (rd.io_options) { *rd.io_options++ = 0; } // undo_io_manager is very slow by now -- safe, but slow: it commits every transaction... setup_tdb(rd.device_name, &io_ptr); io_flags = EXT2_FLAG_64BITS | EXT2_FLAG_RW | EXT2_FLAG_EXCLUSIVE; retval = ext2fs_open2(rd.device_name, rd.io_options, io_flags, 0, 0, io_ptr, &rd.fs); if (retval) { com_err(program_name, retval, _("while trying to open %s"), rd.device_name); printf(_("Couldn't find valid filesystem superblock.\n")); goto close_fd; } if (!force && ((rd.fs->super->s_state & EXT2_ERROR_FS) || ((rd.fs->super->s_state & EXT2_VALID_FS) == 0))) { fprintf(stderr, _("Please run 'e2fsck -f %s' first.\n\n"), rd.device_name); goto close_fs; } // Call main realloc function retval = do_realloc(&rd); if (retval) { com_err(program_name, retval, _("while resizing inode count")); goto close_fs; } close_fs: ext2fs_close(rd.fs); close_fd: if (rd.fs_fd > 0) { close(rd.fs_fd); } return retval; }