Whamcloud - gitweb
AOSP: e2fsdroid: Allow re-use of deduplicated blocks.
[tools/e2fsprogs.git] / resize / resize2fs.c
index 041ff75..8a3d08d 100644 (file)
@@ -4,7 +4,7 @@
  * Copyright (C) 1997, 1998 by Theodore Ts'o and
  *     PowerQuest, Inc.
  *
- * Copyright (C) 1999, 2000 by Theosore Ts'o
+ * Copyright (C) 1999, 2000 by Theodore Ts'o
  *
  * %Begin-Header%
  * This file may be redistributed under the terms of the GNU Public
@@ -61,16 +61,26 @@ static errcode_t move_bg_metadata(ext2_resize_t rfs);
 static errcode_t zero_high_bits_in_inodes(ext2_resize_t rfs);
 
 /*
- * Some helper CPP macros
+ * Some helper functions to check if a block is in a metadata area
  */
-#define IS_BLOCK_BM(fs, i, blk) ((blk) == ext2fs_block_bitmap_loc((fs),(i)))
-#define IS_INODE_BM(fs, i, blk) ((blk) == ext2fs_inode_bitmap_loc((fs),(i)))
+static inline int is_block_bm(ext2_filsys fs, unsigned int grp, blk64_t blk)
+{
+       return blk == ext2fs_block_bitmap_loc(fs, grp);
+}
+
+static inline int is_inode_bm(ext2_filsys fs, unsigned int grp, blk64_t blk)
+{
+       return blk == ext2fs_inode_bitmap_loc(fs, grp);
+}
 
-#define IS_INODE_TB(fs, i, blk) (((blk) >= ext2fs_inode_table_loc((fs), (i))) && \
-                                ((blk) < (ext2fs_inode_table_loc((fs), (i)) + \
-                                          (fs)->inode_blocks_per_group)))
+static int is_inode_tb(ext2_filsys fs, unsigned int grp, blk64_t blk)
+{
+       return blk >= ext2fs_inode_table_loc(fs, grp) &&
+              blk < (ext2fs_inode_table_loc(fs, grp) +
+                     fs->inode_blocks_per_group);
+}
 
-/* Some bigalloc helper macros which are more succint... */
+/* Some bigalloc helper macros which are more succinct... */
 #define B2C(x) EXT2FS_B2C(fs, (x))
 #define C2B(x) EXT2FS_C2B(fs, (x))
 #define EQ_CLSTR(x, y) (B2C(x) == B2C(y))
@@ -196,6 +206,10 @@ errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
                goto errout;
        print_resource_track(rfs, &rtrack, fs->io);
 
+       retval = clear_sparse_super2_last_group(rfs);
+       if (retval)
+               goto errout;
+
        init_resource_track(&rtrack, "calculate_summary_stats", fs->io);
        retval = ext2fs_calculate_summary_stats(rfs->new_fs);
        if (retval)
@@ -214,10 +228,6 @@ errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
                goto errout;
        print_resource_track(rfs, &rtrack, fs->io);
 
-       retval = clear_sparse_super2_last_group(rfs);
-       if (retval)
-               goto errout;
-
        retval = ext2fs_set_gdt_csum(rfs->new_fs);
        if (retval)
                goto errout;
@@ -258,8 +268,7 @@ errout:
 /* Keep the size of the group descriptor region constant */
 static void adjust_reserved_gdt_blocks(ext2_filsys old_fs, ext2_filsys fs)
 {
-       if ((fs->super->s_feature_compat &
-            EXT2_FEATURE_COMPAT_RESIZE_INODE) &&
+       if (ext2fs_has_feature_resize_inode(fs->super) &&
            (old_fs->desc_blocks != fs->desc_blocks)) {
                int new;
 
@@ -291,12 +300,10 @@ static errcode_t resize_group_descriptors(ext2_resize_t rfs, blk64_t new_size)
                return EXT2_ET_INVALID_ARGUMENT;
 
        if (rfs->flags & RESIZE_DISABLE_64BIT) {
-               rfs->new_fs->super->s_feature_incompat &=
-                               ~EXT4_FEATURE_INCOMPAT_64BIT;
+               ext2fs_clear_feature_64bit(rfs->new_fs->super);
                rfs->new_fs->super->s_desc_size = EXT2_MIN_DESC_SIZE;
        } else if (rfs->flags & RESIZE_ENABLE_64BIT) {
-               rfs->new_fs->super->s_feature_incompat |=
-                               EXT4_FEATURE_INCOMPAT_64BIT;
+               ext2fs_set_feature_64bit(rfs->new_fs->super);
                rfs->new_fs->super->s_desc_size = EXT2_MIN_DESC_SIZE_64BIT;
        }
 
@@ -322,8 +329,8 @@ static errcode_t resize_group_descriptors(ext2_resize_t rfs, blk64_t new_size)
                copy_size = EXT2_DESC_SIZE(rfs->new_fs->super);
        for (i = 0; i < rfs->old_fs->group_desc_count; i++) {
                memcpy(n, o, copy_size);
-               n += EXT2_DESC_SIZE(rfs->new_fs->super);
-               o += EXT2_DESC_SIZE(rfs->old_fs->super);
+               n = (char *)n + EXT2_DESC_SIZE(rfs->new_fs->super);
+               o = (char *)o + EXT2_DESC_SIZE(rfs->old_fs->super);
        }
 
        ext2fs_free_mem(&rfs->new_fs->group_desc);
@@ -358,8 +365,7 @@ static errcode_t move_bg_metadata(ext2_resize_t rfs)
        if (retval)
                goto out;
 
-       if (EXT2_HAS_INCOMPAT_FEATURE(rfs->old_fs->super,
-                                     EXT2_FEATURE_INCOMPAT_META_BG)) {
+       if (ext2fs_has_feature_meta_bg(rfs->old_fs->super)) {
                old_desc_blocks = rfs->old_fs->super->s_first_meta_bg;
                new_desc_blocks = rfs->new_fs->super->s_first_meta_bg;
        } else {
@@ -515,7 +521,7 @@ static errcode_t zero_high_bits_in_inodes(ext2_resize_t rfs)
        if (!(rfs->flags & (RESIZE_DISABLE_64BIT | RESIZE_ENABLE_64BIT)))
                return 0;
 
-       if (fs->super->s_creator_os != EXT2_OS_LINUX)
+       if (fs->super->s_creator_os == EXT2_OS_HURD)
                return 0;
 
        retval = ext2fs_open_inode_scan(fs, 0, &scan);
@@ -572,13 +578,13 @@ out:
 }
 
 /*
- * Clean up the bitmaps for unitialized bitmaps
+ * Clean up the bitmaps for uninitialized bitmaps
  */
 static void fix_uninit_block_bitmaps(ext2_filsys fs)
 {
        blk64_t         blk, lblk;
        dgrp_t          g;
-       int             i;
+       unsigned int    i;
 
        if (!ext2fs_has_group_desc_csum(fs))
                return;
@@ -598,7 +604,7 @@ static void fix_uninit_block_bitmaps(ext2_filsys fs)
                ext2fs_mark_block_bitmap2(fs->block_map,
                                          ext2fs_inode_bitmap_loc(fs, g));
                for (i = 0, blk = ext2fs_inode_table_loc(fs, g);
-                    i < (unsigned int) fs->inode_blocks_per_group;
+                    i < fs->inode_blocks_per_group;
                     i++, blk++)
                        ext2fs_mark_block_bitmap2(fs->block_map, blk);
        }
@@ -625,12 +631,12 @@ static errcode_t free_gdp_blocks(ext2_filsys fs,
                                 ext2_filsys old_fs,
                                 dgrp_t group)
 {
-       blk64_t blk;
-       int     j;
-       dgrp_t  i;
+       blk64_t         blk;
+       unsigned int    j;
+       dgrp_t          i;
        ext2fs_block_bitmap bg_map = NULL;
-       errcode_t retval = 0;
-       dgrp_t count = old_fs->group_desc_count - fs->group_desc_count;
+       errcode_t       retval = 0;
+       dgrp_t          count = old_fs->group_desc_count - fs->group_desc_count;
 
        /* If bigalloc, don't free metadata living in the same cluster */
        if (EXT2FS_CLUSTER_RATIO(fs) > 1) {
@@ -692,7 +698,7 @@ errcode_t adjust_fs_info(ext2_filsys fs, ext2_filsys old_fs,
        blk64_t         old_numblocks, numblocks, adjblocks;
        unsigned long   i, j, old_desc_blocks;
        unsigned int    meta_bg, meta_bg_size;
-       int             has_super, csum_flag;
+       int             has_super, csum_flag, has_bg;
        unsigned long long new_inodes;  /* u64 to check for overflow */
        double          percent;
 
@@ -715,7 +721,19 @@ retry:
         */
        overhead = (int) (2 + fs->inode_blocks_per_group);
 
-       if (ext2fs_bg_has_super(fs, fs->group_desc_count - 1))
+       has_bg = 0;
+       if (ext2fs_has_feature_sparse_super2(fs->super)) {
+               /*
+                * We have to do this manually since
+                * super->s_backup_bgs hasn't been set up yet.
+                */
+               if (fs->group_desc_count == 2)
+                       has_bg = fs->super->s_backup_bgs[0] != 0;
+               else
+                       has_bg = fs->super->s_backup_bgs[1] != 0;
+       } else
+               has_bg = ext2fs_bg_has_super(fs, fs->group_desc_count - 1);
+       if (has_bg)
                overhead += 1 + fs->desc_blocks +
                        fs->super->s_reserved_gdt_blocks;
 
@@ -738,7 +756,7 @@ retry:
         */
        new_inodes =(unsigned long long) fs->super->s_inodes_per_group * fs->group_desc_count;
        if (new_inodes > ~0U) {
-               fprintf(stderr, _("inodes (%llu) must be less than %u"),
+               fprintf(stderr, _("inodes (%llu) must be less than %u\n"),
                                   new_inodes, ~0U);
                return EXT2_ET_TOO_MANY_INODES;
        }
@@ -820,10 +838,9 @@ retry:
        if (EXT2_DESC_SIZE(old_fs->super) == EXT2_DESC_SIZE(fs->super))
                adjust_reserved_gdt_blocks(old_fs, fs);
 
-       if ((fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) &&
+       if (ext2fs_has_feature_meta_bg(fs->super) &&
            (fs->super->s_first_meta_bg > fs->desc_blocks)) {
-               fs->super->s_feature_incompat &=
-                       ~EXT2_FEATURE_INCOMPAT_META_BG;
+               ext2fs_clear_feature_meta_bg(fs->super);
                fs->super->s_first_meta_bg = 0;
        }
 
@@ -831,17 +848,16 @@ retry:
         * Update the location of the backup superblocks if the
         * sparse_super2 feature is enabled.
         */
-       if (fs->super->s_feature_compat & EXT4_FEATURE_COMPAT_SPARSE_SUPER2) {
+       if (ext2fs_has_feature_sparse_super2(fs->super)) {
                dgrp_t last_bg = fs->group_desc_count - 1;
                dgrp_t old_last_bg = old_fs->group_desc_count - 1;
 
                if (last_bg > old_last_bg) {
                        if (old_fs->group_desc_count == 1)
                                fs->super->s_backup_bgs[0] = 1;
-                       if (old_fs->group_desc_count == 1 &&
-                           fs->super->s_backup_bgs[0])
-                               fs->super->s_backup_bgs[0] = last_bg;
-                       else if (fs->super->s_backup_bgs[1])
+                       if ((old_fs->group_desc_count < 3 &&
+                            fs->group_desc_count > 2) ||
+                           fs->super->s_backup_bgs[1])
                                fs->super->s_backup_bgs[1] = last_bg;
                } else if (last_bg < old_last_bg) {
                        if (fs->super->s_backup_bgs[0] > last_bg)
@@ -904,10 +920,11 @@ retry:
        group_block = ext2fs_group_first_block2(fs,
                                                old_fs->group_desc_count);
        csum_flag = ext2fs_has_group_desc_csum(fs);
-       if (!getenv("RESIZE2FS_FORCE_ITABLE_INIT") &&
-           access("/sys/fs/ext4/features/lazy_itable_init", F_OK) == 0)
+       if (getenv("RESIZE2FS_FORCE_LAZY_ITABLE_INIT") ||
+           (!getenv("RESIZE2FS_FORCE_ITABLE_INIT") &&
+            access("/sys/fs/ext4/features/lazy_itable_init", F_OK) == 0))
                lazy_itable_init = 1;
-       if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)
+       if (ext2fs_has_feature_meta_bg(fs->super))
                old_desc_blocks = fs->super->s_first_meta_bg;
        else
                old_desc_blocks = fs->desc_blocks +
@@ -916,7 +933,7 @@ retry:
        /*
         * If we changed the number of block_group descriptor blocks,
         * we need to make sure they are all marked as reserved in the
-        * file systems's block allocation map.
+        * filesystem's block allocation map.
         */
        for (i = 0; i < old_fs->group_desc_count; i++)
                ext2fs_reserve_super_and_bgd(fs, i, fs->block_map);
@@ -948,8 +965,7 @@ retry:
                }
                meta_bg_size = EXT2_DESC_PER_BLOCK(fs->super);
                meta_bg = i / meta_bg_size;
-               if (!(fs->super->s_feature_incompat &
-                     EXT2_FEATURE_INCOMPAT_META_BG) ||
+               if (!ext2fs_has_feature_meta_bg(fs->super) ||
                    (meta_bg < fs->super->s_first_meta_bg)) {
                        if (has_super) {
                                for (j=0; j < old_desc_blocks; j++)
@@ -1172,7 +1188,7 @@ static void mark_fs_metablock(ext2_resize_t rfs,
                              ext2fs_block_bitmap meta_bmap,
                              int group, blk64_t blk)
 {
-       ext2_filsys     fs = rfs->new_fs;
+       ext2_filsys fs = rfs->new_fs;
 
        ext2fs_mark_block_bitmap2(rfs->reserve_blocks, blk);
        ext2fs_block_alloc_stats2(fs, blk, +1);
@@ -1182,36 +1198,36 @@ static void mark_fs_metablock(ext2_resize_t rfs,
         * or the inode tables.  If not, and the block is in use, then
         * mark it as a block to be moved.
         */
-       if (IS_BLOCK_BM(fs, group, blk)) {
+       if (is_block_bm(fs, group, blk)) {
                ext2fs_block_bitmap_loc_set(fs, group, 0);
                rfs->needed_blocks++;
                return;
        }
-       if (IS_INODE_BM(fs, group, blk)) {
+       if (is_inode_bm(fs, group, blk)) {
                ext2fs_inode_bitmap_loc_set(fs, group, 0);
                rfs->needed_blocks++;
                return;
        }
-       if (IS_INODE_TB(fs, group, blk)) {
+       if (is_inode_tb(fs, group, blk)) {
                ext2fs_inode_table_loc_set(fs, group, 0);
                rfs->needed_blocks++;
                return;
        }
-       if (fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_FLEX_BG) {
+       if (ext2fs_has_feature_flex_bg(fs->super)) {
                dgrp_t i;
 
-               for (i=0; i < rfs->old_fs->group_desc_count; i++) {
-                       if (IS_BLOCK_BM(fs, i, blk)) {
+               for (i = 0; i < rfs->old_fs->group_desc_count; i++) {
+                       if (is_block_bm(fs, i, blk)) {
                                ext2fs_block_bitmap_loc_set(fs, i, 0);
                                rfs->needed_blocks++;
                                return;
                        }
-                       if (IS_INODE_BM(fs, i, blk)) {
+                       if (is_inode_bm(fs, i, blk)) {
                                ext2fs_inode_bitmap_loc_set(fs, i, 0);
                                rfs->needed_blocks++;
                                return;
                        }
-                       if (IS_INODE_TB(fs, i, blk)) {
+                       if (is_inode_tb(fs, i, blk)) {
                                ext2fs_inode_table_loc_set(fs, i, 0);
                                rfs->needed_blocks++;
                                return;
@@ -1241,7 +1257,8 @@ static void mark_fs_metablock(ext2_resize_t rfs,
  */
 static errcode_t blocks_to_move(ext2_resize_t rfs)
 {
-       int             j, has_super;
+       unsigned int    j;
+       int             has_super;
        dgrp_t          i, max_groups, g;
        blk64_t         blk, group_blk;
        blk64_t         old_blocks, new_blocks, group_end, cluster_freed;
@@ -1333,12 +1350,12 @@ static errcode_t blocks_to_move(ext2_resize_t rfs)
                ext2fs_mark_block_bitmap2(rfs->reserve_blocks, blk);
        }
 
-       if (old_fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)
+       if (ext2fs_has_feature_meta_bg(old_fs->super))
                old_blocks = old_fs->super->s_first_meta_bg;
        else
                old_blocks = old_fs->desc_blocks +
                        old_fs->super->s_reserved_gdt_blocks;
-       if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)
+       if (ext2fs_has_feature_meta_bg(fs->super))
                new_blocks = fs->super->s_first_meta_bg;
        else
                new_blocks = fs->desc_blocks + fs->super->s_reserved_gdt_blocks;
@@ -1411,8 +1428,7 @@ static errcode_t blocks_to_move(ext2_resize_t rfs)
         * gets interesting....
         */
        meta_bg_size = EXT2_DESC_PER_BLOCK(fs->super);
-       flex_bg = fs->super->s_feature_incompat &
-               EXT4_FEATURE_INCOMPAT_FLEX_BG;
+       flex_bg = ext2fs_has_feature_flex_bg(fs->super);
        /* first reserve all of the existing fs meta blocks */
        for (i = 0; i < max_groups; i++) {
                has_super = ext2fs_bg_has_super(fs, i);
@@ -1420,8 +1436,7 @@ static errcode_t blocks_to_move(ext2_resize_t rfs)
                        mark_fs_metablock(rfs, meta_bmap, i, group_blk);
 
                meta_bg = i / meta_bg_size;
-               if (!(fs->super->s_feature_incompat &
-                     EXT2_FEATURE_INCOMPAT_META_BG) ||
+               if (!ext2fs_has_feature_meta_bg(fs->super) ||
                    (meta_bg < fs->super->s_first_meta_bg)) {
                        if (has_super) {
                                for (blk = group_blk+1;
@@ -1489,7 +1504,7 @@ static errcode_t blocks_to_move(ext2_resize_t rfs)
 
                /*
                 * For those structures that have changed, we need to
-                * do bookkeepping.
+                * do bookkeeping.
                 */
                if (ext2fs_block_bitmap_loc(old_fs, i) !=
                    (blk = ext2fs_block_bitmap_loc(fs, i))) {
@@ -1614,11 +1629,13 @@ static blk64_t get_new_block(ext2_resize_t rfs)
        }
 }
 
-static errcode_t resize2fs_get_alloc_block(ext2_filsys fs, blk64_t goal,
+static errcode_t resize2fs_get_alloc_block(ext2_filsys fs,
+                                          blk64_t goal EXT2FS_ATTR((unused)),
                                           blk64_t *ret)
 {
        ext2_resize_t rfs = (ext2_resize_t) fs->priv_data;
        blk64_t blk;
+       int group;
 
        blk = get_new_block(rfs);
        if (!blk)
@@ -1631,6 +1648,12 @@ static errcode_t resize2fs_get_alloc_block(ext2_filsys fs, blk64_t goal,
 
        ext2fs_mark_block_bitmap2(rfs->old_fs->block_map, blk);
        ext2fs_mark_block_bitmap2(rfs->new_fs->block_map, blk);
+
+       group = ext2fs_group_of_blk2(rfs->old_fs, blk);
+       ext2fs_clear_block_uninit(rfs->old_fs, group);
+       group = ext2fs_group_of_blk2(rfs->new_fs, blk);
+       ext2fs_clear_block_uninit(rfs->new_fs, group);
+
        *ret = (blk64_t) blk;
        return 0;
 }
@@ -1641,8 +1664,7 @@ static errcode_t block_mover(ext2_resize_t rfs)
        ext2_filsys             fs = rfs->new_fs;
        ext2_filsys             old_fs = rfs->old_fs;
        errcode_t               retval;
-       __u64                   size;
-       int                     c;
+       __u64                   c, size;
        int                     to_move, moved;
        ext2_badblocks_list     badblock_list = 0;
        int                     bb_modified = 0;
@@ -1874,7 +1896,7 @@ static errcode_t migrate_ea_block(ext2_resize_t rfs, ext2_ino_t ino,
        errcode_t err = 0;
 
        /* No EA block or no remapping?  Quit early. */
-       if (ext2fs_file_acl_block(rfs->old_fs, inode) == 0 && !rfs->bmap)
+       if (ext2fs_file_acl_block(rfs->old_fs, inode) == 0 || !rfs->bmap)
                return 0;
        new_block = extent_translate(rfs->old_fs, rfs->bmap,
                ext2fs_file_acl_block(rfs->old_fs, inode));
@@ -1885,8 +1907,7 @@ static errcode_t migrate_ea_block(ext2_resize_t rfs, ext2_ino_t ino,
        ext2fs_file_acl_block_set(rfs->old_fs, inode, new_block);
 
        /* Update checksum */
-       if (EXT2_HAS_RO_COMPAT_FEATURE(rfs->new_fs->super,
-                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) {
+       if (ext2fs_has_feature_metadata_csum(rfs->new_fs->super)) {
                err = ext2fs_get_mem(rfs->old_fs->blocksize, &buf);
                if (err)
                        return err;
@@ -1906,64 +1927,153 @@ out:
        return err;
 }
 
-/* Rewrite extents */
-static errcode_t rewrite_extents(ext2_filsys fs, ext2_ino_t ino)
+static void quiet_com_err_proc(const char *whoami EXT2FS_ATTR((unused)),
+                              errcode_t code EXT2FS_ATTR((unused)),
+                              const char *fmt EXT2FS_ATTR((unused)),
+                              va_list args EXT2FS_ATTR((unused)))
 {
-       ext2_extent_handle_t    handle;
-       struct ext2fs_extent    extent;
-       errcode_t               errcode;
-       struct ext2_extent_info info;
+}
 
-       errcode = ext2fs_extent_open(fs, ino, &handle);
-       if (errcode)
-               return errcode;
+static int fix_ea_entries(ext2_extent imap, struct ext2_ext_attr_entry *entry,
+                         struct ext2_ext_attr_entry *end, ext2_ino_t last_ino)
+{
+       int modified = 0;
+       ext2_ino_t new_ino;
+
+       while (entry < end && !EXT2_EXT_IS_LAST_ENTRY(entry)) {
+               if (entry->e_value_inum > last_ino) {
+                       new_ino = ext2fs_extent_translate(imap,
+                                                         entry->e_value_inum);
+                       entry->e_value_inum = new_ino;
+                       modified = 1;
+               }
+               entry = EXT2_EXT_ATTR_NEXT(entry);
+       }
+       return modified;
+}
 
-       errcode = ext2fs_extent_get(handle, EXT2_EXTENT_ROOT, &extent);
-       if (errcode)
+static int fix_ea_ibody_entries(ext2_extent imap,
+                               struct ext2_inode_large *inode, int inode_size,
+                               ext2_ino_t last_ino)
+{
+       struct ext2_ext_attr_entry *start, *end;
+       __u32 *ea_magic;
+
+       if (inode->i_extra_isize == 0)
+               return 0;
+
+       ea_magic = (__u32 *)((char *)inode + EXT2_GOOD_OLD_INODE_SIZE +
+                               inode->i_extra_isize);
+       if (*ea_magic != EXT2_EXT_ATTR_MAGIC)
+               return 0;
+
+       start = (struct ext2_ext_attr_entry *)(ea_magic + 1);
+       end = (struct ext2_ext_attr_entry *)((char *)inode + inode_size);
+
+       return fix_ea_entries(imap, start, end, last_ino);
+}
+
+static int fix_ea_block_entries(ext2_extent imap, char *block_buf,
+                               unsigned int blocksize, ext2_ino_t last_ino)
+{
+       struct ext2_ext_attr_header *header;
+       struct ext2_ext_attr_entry *start, *end;
+
+       header = (struct ext2_ext_attr_header *)block_buf;
+       start = (struct ext2_ext_attr_entry *)(header+1);
+       end = (struct ext2_ext_attr_entry *)(block_buf + blocksize);
+
+       return fix_ea_entries(imap, start, end, last_ino);
+}
+
+/* A simple LRU cache to check recently processed blocks. */
+struct blk_cache {
+       int cursor;
+       blk64_t blks[4];
+};
+
+#define BLK_IN_CACHE(b,c) ((b) == (c).blks[0] || (b) == (c).blks[1] || \
+                          (b) == (c).blks[2] || (b) == (c).blks[3])
+#define BLK_ADD_CACHE(b,c) {                   \
+       (c).blks[(c).cursor] = (b);             \
+       (c).cursor = ((c).cursor + 1) % 4;      \
+}
+
+static errcode_t fix_ea_inode_refs(ext2_resize_t rfs, struct ext2_inode *inode,
+                                  char *block_buf, ext2_ino_t last_ino)
+{
+       ext2_filsys     fs = rfs->new_fs;
+       ext2_inode_scan scan = NULL;
+       ext2_ino_t      ino;
+       int             inode_size = EXT2_INODE_SIZE(fs->super);
+       blk64_t         blk;
+       int             modified;
+       struct blk_cache blk_cache;
+       struct ext2_ext_attr_header *header;
+       errcode_t               retval;
+
+       memset(&blk_cache, 0, sizeof(blk_cache));
+
+       header = (struct ext2_ext_attr_header *)block_buf;
+
+       retval = ext2fs_open_inode_scan(fs, 0, &scan);
+       if (retval)
                goto out;
 
-       do {
-               errcode = ext2fs_extent_get_info(handle, &info);
-               if (errcode)
+       while (1) {
+               retval = ext2fs_get_next_inode_full(scan, &ino, inode,
+                                                   inode_size);
+               if (retval)
+                       goto out;
+               if (!ino)
                        break;
 
-               /*
-                * If this is the first extent in an extent block that we
-                * haven't visited, rewrite the extent to force the ETB
-                * checksum to be rewritten.
-                */
-               if (info.curr_entry == 1 && info.curr_level != 0 &&
-                   !(extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT)) {
-                       errcode = ext2fs_extent_replace(handle, 0, &extent);
-                       if (errcode)
-                               break;
-               }
+               if (inode->i_links_count == 0 && ino != EXT2_RESIZE_INO)
+                       continue; /* inode not in use */
 
-               /* Skip to the end of a block of leaf nodes */
-               if (extent.e_flags & EXT2_EXTENT_FLAGS_LEAF) {
-                       errcode = ext2fs_extent_get(handle,
-                                                   EXT2_EXTENT_LAST_SIB,
-                                                   &extent);
-                       if (errcode)
-                               break;
+               if (inode_size != EXT2_GOOD_OLD_INODE_SIZE) {
+                       modified = fix_ea_ibody_entries(rfs->imap,
+                                       (struct ext2_inode_large *)inode,
+                                       inode_size, last_ino);
+                       if (modified) {
+                               retval = ext2fs_write_inode_full(fs, ino, inode,
+                                                                inode_size);
+                               if (retval)
+                                       goto out;
+                       }
                }
 
-               errcode = ext2fs_extent_get(handle, EXT2_EXTENT_NEXT, &extent);
-       } while (errcode == 0);
+               blk = ext2fs_file_acl_block(fs, inode);
+               if (blk && !BLK_IN_CACHE(blk, blk_cache)) {
+                       retval = ext2fs_read_ext_attr3(fs, blk, block_buf, ino);
+                       if (retval)
+                               goto out;
 
+                       modified = fix_ea_block_entries(rfs->imap, block_buf,
+                                                       fs->blocksize,
+                                                       last_ino);
+                       if (modified) {
+                               retval = ext2fs_write_ext_attr3(fs, blk,
+                                                               block_buf, ino);
+                               if (retval)
+                                       goto out;
+                               /*
+                                * If refcount is greater than 1, we might see
+                                * the same block referenced by other inodes
+                                * later.
+                                */
+                               if (header->h_refcount > 1)
+                                       BLK_ADD_CACHE(blk, blk_cache);
+                       }
+               }
+       }
+       retval = 0;
 out:
-       /* Ok if we run off the end */
-       if (errcode == EXT2_ET_EXTENT_NO_NEXT)
-               errcode = 0;
-       ext2fs_extent_free(handle);
-       return errcode;
-}
+       if (scan)
+               ext2fs_close_inode_scan(scan);
+       return retval;
 
-static void quiet_com_err_proc(const char *whoami, errcode_t code,
-                              const char *fmt, va_list args)
-{
 }
-
 static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
 {
        struct process_block_struct     pb;
@@ -1974,6 +2084,7 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
        char                    *block_buf = 0;
        ext2_ino_t              start_to_move;
        int                     inode_size;
+       int                     update_ea_inode_refs = 0;
 
        if ((rfs->old_fs->group_desc_count <=
             rfs->new_fs->group_desc_count) &&
@@ -2046,7 +2157,15 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
 
                ext2fs_inode_alloc_stats2(rfs->new_fs, new_inode, +1,
                                          pb.is_dir);
-               inode->i_ctime = time(0);
+               /*
+                * i_ctime field in xattr inodes contain a portion of the ref
+                * count, do not overwrite.
+                */
+               if (inode->i_flags & EXT4_EA_INODE_FL)
+                       update_ea_inode_refs = 1;
+               else
+                       inode->i_ctime = time(0);
+
                retval = ext2fs_write_inode_full(rfs->old_fs, new_inode,
                                                inode, inode_size);
                if (retval)
@@ -2072,32 +2191,20 @@ remap_blocks:
                if (retval)
                        goto errout;
 
-               /* Rewrite extent block checksums with new inode number */
-               if (EXT2_HAS_RO_COMPAT_FEATURE(rfs->old_fs->super,
-                                      EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) &&
-                   (inode->i_flags & EXT4_EXTENTS_FL)) {
-                       rfs->old_fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
-                       retval = rewrite_extents(rfs->old_fs, new_inode);
-                       rfs->old_fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
-                       if (retval)
-                               goto errout;
-               }
-
                /*
                 * Update inodes to point to new blocks; schedule directory
                 * blocks for inode remapping.  Need to write out dir blocks
                 * with new inode numbers if we have metadata_csum enabled.
                 */
+               rfs->old_fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
                if (ext2fs_inode_has_valid_blocks2(rfs->old_fs, inode) &&
                    (rfs->bmap || pb.is_dir)) {
                        pb.ino = new_inode;
                        pb.old_ino = ino;
                        pb.has_extents = inode->i_flags & EXT4_EXTENTS_FL;
-                       rfs->old_fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
                        retval = ext2fs_block_iterate3(rfs->old_fs,
                                                       new_inode, 0, block_buf,
                                                       process_block, &pb);
-                       rfs->old_fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
                        if (retval)
                                goto errout;
                        if (pb.error) {
@@ -2112,11 +2219,29 @@ remap_blocks:
                        if (retval)
                                goto errout;
                }
+
+               /* Fix up extent block checksums with the new inode number */
+               if (ext2fs_has_feature_metadata_csum(rfs->old_fs->super) &&
+                   (inode->i_flags & EXT4_EXTENTS_FL)) {
+                       retval = ext2fs_fix_extents_checksums(rfs->old_fs,
+                                                             new_inode, NULL);
+                       if (retval)
+                               goto errout;
+               }
+       }
+
+       if (update_ea_inode_refs &&
+           ext2fs_has_feature_ea_inode(rfs->new_fs->super)) {
+               retval = fix_ea_inode_refs(rfs, inode, block_buf,
+                                          start_to_move);
+               if (retval)
+                       goto errout;
        }
        io_channel_flush(rfs->old_fs->io);
 
 errout:
        reset_com_err_hook();
+       rfs->old_fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
        if (rfs->bmap) {
                ext2fs_free_extent_table(rfs->bmap);
                rfs->bmap = 0;
@@ -2169,8 +2294,7 @@ static int check_and_change_inodes(ext2_ino_t dir,
         * If we have checksums enabled and the inode wasn't present in the
         * old fs, then we must rewrite all dir blocks with new checksums.
         */
-       if (EXT2_HAS_RO_COMPAT_FEATURE(is->rfs->old_fs->super,
-                                      EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) &&
+       if (ext2fs_has_feature_metadata_csum(is->rfs->old_fs->super) &&
            !ext2fs_test_inode_bitmap2(is->rfs->old_fs->inode_map, dir))
                ret |= DIRENT_CHANGED;
 
@@ -2278,7 +2402,8 @@ static errcode_t move_itables(ext2_resize_t rfs)
        char            *cp;
        blk64_t         old_blk, new_blk, blk, cluster_freed;
        errcode_t       retval;
-       int             j, to_move, moved;
+       int             to_move, moved;
+       unsigned int    j;
        ext2fs_block_bitmap     new_bmap = NULL;
 
        max_groups = fs->group_desc_count;
@@ -2439,7 +2564,7 @@ static errcode_t clear_sparse_super2_last_group(ext2_resize_t rfs)
        blk64_t         sb, old_desc;
        blk_t           num;
 
-       if (!(fs->super->s_feature_compat & EXT4_FEATURE_COMPAT_SPARSE_SUPER2))
+       if (!ext2fs_has_feature_sparse_super2(fs->super))
                return 0;
 
        if (last_bg <= old_last_bg)
@@ -2457,6 +2582,9 @@ static errcode_t clear_sparse_super2_last_group(ext2_resize_t rfs)
            fs->super->s_backup_bgs[1] == old_last_bg)
                return 0;
 
+       if (old_last_bg == 0)
+               return 0;
+
        retval = ext2fs_super_and_bgd_loc2(rfs->old_fs, old_last_bg,
                                           &sb, &old_desc, NULL, &num);
        if (retval)
@@ -2489,7 +2617,7 @@ static errcode_t reserve_sparse_super2_last_group(ext2_resize_t rfs,
        blk_t           i, num;
        int             realloc = 0;
 
-       if (!(fs->super->s_feature_compat & EXT4_FEATURE_COMPAT_SPARSE_SUPER2))
+       if (!ext2fs_has_feature_sparse_super2(fs->super))
                return 0;
 
        if (last_bg >= old_last_bg)
@@ -2573,8 +2701,7 @@ static errcode_t fix_resize_inode(ext2_filsys fs)
        struct ext2_inode       inode;
        errcode_t               retval;
 
-       if (!(fs->super->s_feature_compat &
-             EXT2_FEATURE_COMPAT_RESIZE_INODE))
+       if (!ext2fs_has_feature_resize_inode(fs->super))
                return 0;
 
        retval = ext2fs_read_inode(fs, EXT2_RESIZE_INO, &inode);
@@ -2631,7 +2758,7 @@ static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs)
        uninit = ext2fs_bg_flags_test(fs, group, EXT2_BG_BLOCK_UNINIT);
        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)
+       if (ext2fs_has_feature_meta_bg(fs->super))
                old_desc_blocks = fs->super->s_first_meta_bg;
        else
                old_desc_blocks = fs->desc_blocks +
@@ -2668,8 +2795,7 @@ static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs)
                        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)
+                       if (ext2fs_has_feature_meta_bg(fs->super))
                                old_desc_blocks = fs->super->s_first_meta_bg;
                        else
                                old_desc_blocks = fs->desc_blocks +
@@ -2721,7 +2847,7 @@ static errcode_t fix_sb_journal_backup(ext2_filsys fs)
        errcode_t         retval;
        struct ext2_inode inode;
 
-       if (!(fs->super->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL))
+       if (!ext2fs_has_feature_journal(fs->super))
                return 0;
 
        /* External journal? Nothing to do. */
@@ -2761,7 +2887,7 @@ static int calc_group_overhead(ext2_filsys fs, blk64_t grp,
 
 
 /*
- * calcluate the minimum number of blocks the given fs can be resized to
+ * calculate the minimum number of blocks the given fs can be resized to
  */
 blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
 {
@@ -2793,18 +2919,18 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
        /*
         * number of old-style block group descriptor blocks
         */
-       if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)
+       if (ext2fs_has_feature_meta_bg(fs->super))
                old_desc_blocks = fs->super->s_first_meta_bg;
        else
                old_desc_blocks = fs->desc_blocks +
                        fs->super->s_reserved_gdt_blocks;
 
        /* calculate how many blocks are needed for data */
-       data_needed = ext2fs_blocks_count(fs->super) -
-               ext2fs_free_blocks_count(fs->super);
-
-       for (grp = 0; grp < fs->group_desc_count; grp++)
+       data_needed = ext2fs_blocks_count(fs->super);
+       for (grp = 0; grp < fs->group_desc_count; grp++) {
                data_needed -= calc_group_overhead(fs, grp, old_desc_blocks);
+               data_needed -= ext2fs_bg_free_blocks_count(fs, grp);
+       }
 #ifdef RESIZE2FS_DEBUG
        if (flags & RESIZE_DEBUG_MIN_CALC)
                printf("fs requires %llu data blocks.\n", data_needed);
@@ -2816,7 +2942,7 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
         * guaranteed to finish.
         */
        flex_groups = groups;
-       if (fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_FLEX_BG) {
+       if (ext2fs_has_feature_flex_bg(fs->super)) {
                dgrp_t remainder = groups & (flexbg_size - 1);
 
                flex_groups += flexbg_size - remainder;
@@ -2854,7 +2980,7 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
 #endif
 
        /*
-        * if we need more group descriptors in order to accomodate our data
+        * if we need more group descriptors in order to accommodate our data
         * then we need to add them here
         */
        blks_needed = data_needed;
@@ -2874,8 +3000,7 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
 
                grp = flex_groups;
                groups += extra_grps;
-               if (!(fs->super->s_feature_incompat &
-                     EXT4_FEATURE_INCOMPAT_FLEX_BG))
+               if (!ext2fs_has_feature_flex_bg(fs->super))
                        flex_groups = groups;
                else if (groups > flex_groups) {
                        dgrp_t r = groups & (flexbg_size - 1);
@@ -2911,7 +3036,7 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
 
        /* now for the fun voodoo */
        grp = groups - 1;
-       if ((fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_FLEX_BG) &&
+       if (ext2fs_has_feature_flex_bg(fs->super) &&
            (grp & ~(flexbg_size - 1)) == 0)
                grp = grp & ~(flexbg_size - 1);
        overhead = 0;
@@ -2997,7 +3122,7 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
         * with each data block needs to be in its own extent, and
         * with each inode needing at least one extent block.
         */
-       if (fs->super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_EXTENTS) {
+       if (ext2fs_has_feature_extents(fs->super)) {
                blk64_t safe_margin = (ext2fs_blocks_count(fs->super) -
                                       blks_needed)/500;
                unsigned int exts_per_blk = (fs->blocksize /