Whamcloud - gitweb
libext2fs: add gnu.translator support
[tools/e2fsprogs.git] / lib / ext2fs / csum.c
index 262eb75..9b0b790 100644 (file)
 #define STATIC static
 #endif
 
+void ext2fs_init_csum_seed(ext2_filsys fs)
+{
+       if (ext2fs_has_feature_csum_seed(fs->super))
+               fs->csum_seed = fs->super->s_checksum_seed;
+       else if (ext2fs_has_feature_metadata_csum(fs->super) ||
+                ext2fs_has_feature_ea_inode(fs->super))
+               fs->csum_seed = ext2fs_crc32c_le(~0, fs->super->s_uuid,
+                                                sizeof(fs->super->s_uuid));
+}
+
+static __u32 ext2fs_mmp_csum(ext2_filsys fs, struct mmp_struct *mmp)
+{
+       int offset = offsetof(struct mmp_struct, mmp_checksum);
+
+       return ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)mmp, offset);
+}
+
+int ext2fs_mmp_csum_verify(ext2_filsys fs, struct mmp_struct *mmp)
+{
+       __u32 calculated;
+
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 1;
+
+       calculated = ext2fs_mmp_csum(fs, mmp);
+
+       return ext2fs_le32_to_cpu(mmp->mmp_checksum) == calculated;
+}
+
+errcode_t ext2fs_mmp_csum_set(ext2_filsys fs, struct mmp_struct *mmp)
+{
+       __u32 crc;
+
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 0;
+
+       crc = ext2fs_mmp_csum(fs, mmp);
+       mmp->mmp_checksum = ext2fs_cpu_to_le32(crc);
+
+       return 0;
+}
+
+int ext2fs_verify_csum_type(ext2_filsys fs, struct ext2_super_block *sb)
+{
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 1;
+
+       return sb->s_checksum_type == EXT2_CRC32C_CHKSUM;
+}
+
+static __u32 ext2fs_superblock_csum(ext2_filsys fs EXT2FS_ATTR((unused)),
+                                   struct ext2_super_block *sb)
+{
+       int offset = offsetof(struct ext2_super_block, s_checksum);
+
+       return ext2fs_crc32c_le(~0, (unsigned char *)sb, offset);
+}
+
+/* NOTE: The input to this function MUST be in LE order */
+int ext2fs_superblock_csum_verify(ext2_filsys fs, struct ext2_super_block *sb)
+{
+       __u32 flag, calculated;
+
+       if (fs->flags & EXT2_FLAG_SWAP_BYTES)
+               flag = EXT4_FEATURE_RO_COMPAT_METADATA_CSUM;
+       else
+               flag = ext2fs_cpu_to_le32(EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
+
+       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super, flag))
+               return 1;
+
+       calculated = ext2fs_superblock_csum(fs, sb);
+
+       return ext2fs_le32_to_cpu(sb->s_checksum) == calculated;
+}
+
+/* NOTE: The input to this function MUST be in LE order */
+errcode_t ext2fs_superblock_csum_set(ext2_filsys fs,
+                                    struct ext2_super_block *sb)
+{
+       __u32 flag, crc;
+
+       if (fs->flags & EXT2_FLAG_SWAP_BYTES)
+               flag = EXT4_FEATURE_RO_COMPAT_METADATA_CSUM;
+       else
+               flag = ext2fs_cpu_to_le32(EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
+
+       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super, flag))
+               return 0;
+
+       crc = ext2fs_superblock_csum(fs, sb);
+       sb->s_checksum = ext2fs_cpu_to_le32(crc);
+
+       return 0;
+}
+
+static errcode_t ext2fs_ext_attr_block_csum(ext2_filsys fs,
+                                           ext2_ino_t inum EXT2FS_ATTR((unused)),
+                                           blk64_t block,
+                                           struct ext2_ext_attr_header *hdr,
+                                           __u32 *crc)
+{
+       char *buf = (char *)hdr;
+       __u32 old_crc = hdr->h_checksum;
+
+       hdr->h_checksum = 0;
+       block = ext2fs_cpu_to_le64(block);
+       *crc = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)&block,
+                               sizeof(block));
+       *crc = ext2fs_crc32c_le(*crc, (unsigned char *)buf, fs->blocksize);
+       hdr->h_checksum = old_crc;
+
+       return 0;
+}
+
+int ext2fs_ext_attr_block_csum_verify(ext2_filsys fs, ext2_ino_t inum,
+                                     blk64_t block,
+                                     struct ext2_ext_attr_header *hdr)
+{
+       __u32 calculated;
+       errcode_t retval;
+
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 1;
+
+       retval = ext2fs_ext_attr_block_csum(fs, inum, block, hdr, &calculated);
+       if (retval)
+               return 0;
+
+       return ext2fs_le32_to_cpu(hdr->h_checksum) == calculated;
+}
+
+errcode_t ext2fs_ext_attr_block_csum_set(ext2_filsys fs, ext2_ino_t inum,
+                                        blk64_t block,
+                                        struct ext2_ext_attr_header *hdr)
+{
+       errcode_t retval;
+       __u32 crc;
+
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 0;
+
+       retval = ext2fs_ext_attr_block_csum(fs, inum, block, hdr, &crc);
+       if (retval)
+               return retval;
+       hdr->h_checksum = ext2fs_cpu_to_le32(crc);
+       return 0;
+}
+
 static __u16 do_nothing16(__u16 x)
 {
        return x;
@@ -58,11 +207,11 @@ static errcode_t __get_dx_countlimit(ext2_filsys fs,
        if (rec_len == fs->blocksize && translate(dirent->name_len) == 0)
                count_offset = 8;
        else if (rec_len == 12) {
-               dp = (struct ext2_dir_entry *)(((void *)dirent) + rec_len);
+               dp = (struct ext2_dir_entry *)(((char *)dirent) + rec_len);
                rec_len = translate(dp->rec_len);
                if (rec_len != fs->blocksize - 12)
                        return EXT2_ET_DB_NOT_FOUND;
-               root = (struct ext2_dx_root_info *)(((void *)dp + 12));
+               root = (struct ext2_dx_root_info *)(((char *)dp + 12));
                if (root->reserved_zero ||
                    root->info_length != sizeof(struct ext2_dx_root_info))
                        return EXT2_ET_DB_NOT_FOUND;
@@ -70,7 +219,7 @@ static errcode_t __get_dx_countlimit(ext2_filsys fs,
        } else
                return EXT2_ET_DB_NOT_FOUND;
 
-       c = (struct ext2_dx_countlimit *)(((void *)dirent) + count_offset);
+       c = (struct ext2_dx_countlimit *)(((char *)dirent) + count_offset);
        max_sane_entries = (fs->blocksize - count_offset) /
                           sizeof(struct ext2_dx_entry);
        if (ext2fs_le16_to_cpu(c->limit) > max_sane_entries ||
@@ -93,6 +242,124 @@ errcode_t ext2fs_get_dx_countlimit(ext2_filsys fs,
        return __get_dx_countlimit(fs, dirent, cc, offset, 0);
 }
 
+void ext2fs_initialize_dirent_tail(ext2_filsys fs,
+                                  struct ext2_dir_entry_tail *t)
+{
+       memset(t, 0, sizeof(struct ext2_dir_entry_tail));
+       ext2fs_set_rec_len(fs, sizeof(struct ext2_dir_entry_tail),
+                          (struct ext2_dir_entry *)t);
+       t->det_reserved_name_len = EXT2_DIR_NAME_LEN_CSUM;
+}
+
+static errcode_t __get_dirent_tail(ext2_filsys fs,
+                                  struct ext2_dir_entry *dirent,
+                                  struct ext2_dir_entry_tail **tt,
+                                  int need_swab)
+{
+       struct ext2_dir_entry *d;
+       void *top;
+       struct ext2_dir_entry_tail *t;
+       unsigned int rec_len;
+       errcode_t retval = 0;
+       __u16 (*translate)(__u16) = (need_swab ? disk_to_host16 : do_nothing16);
+
+       d = dirent;
+       top = EXT2_DIRENT_TAIL(dirent, fs->blocksize);
+
+       while ((void *) d < top) {
+               rec_len = translate(d->rec_len);
+               if ((rec_len < 8) || (rec_len & 0x03))
+                       return EXT2_ET_DIR_CORRUPTED;
+               d = (struct ext2_dir_entry *)(((char *)d) + rec_len);
+       }
+
+       if ((char *)d > ((char *)dirent + fs->blocksize))
+                       return EXT2_ET_DIR_CORRUPTED;
+       if (d != top)
+               return EXT2_ET_DIR_NO_SPACE_FOR_CSUM;
+
+       t = (struct ext2_dir_entry_tail *)d;
+       if (t->det_reserved_zero1 ||
+           translate(t->det_rec_len) != sizeof(struct ext2_dir_entry_tail) ||
+           translate(t->det_reserved_name_len) != EXT2_DIR_NAME_LEN_CSUM)
+               return EXT2_ET_DIR_NO_SPACE_FOR_CSUM;
+
+       if (tt)
+               *tt = t;
+       return retval;
+}
+
+int ext2fs_dirent_has_tail(ext2_filsys fs, struct ext2_dir_entry *dirent)
+{
+       return __get_dirent_tail(fs, dirent, NULL, 0) !=
+               EXT2_ET_DIR_NO_SPACE_FOR_CSUM;
+}
+
+static errcode_t ext2fs_dirent_csum(ext2_filsys fs, ext2_ino_t inum,
+                                   struct ext2_dir_entry *dirent, __u32 *crc,
+                                   int size)
+{
+       errcode_t retval;
+       char *buf = (char *)dirent;
+       __u32 gen;
+       struct ext2_inode inode;
+
+       retval = ext2fs_read_inode(fs, inum, &inode);
+       if (retval)
+               return retval;
+
+       inum = ext2fs_cpu_to_le32(inum);
+       gen = ext2fs_cpu_to_le32(inode.i_generation);
+       *crc = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)&inum,
+                               sizeof(inum));
+       *crc = ext2fs_crc32c_le(*crc, (unsigned char *)&gen, sizeof(gen));
+       *crc = ext2fs_crc32c_le(*crc, (unsigned char *)buf, size);
+
+       return 0;
+}
+
+int ext2fs_dirent_csum_verify(ext2_filsys fs, ext2_ino_t inum,
+                             struct ext2_dir_entry *dirent)
+{
+       errcode_t retval;
+       __u32 calculated;
+       struct ext2_dir_entry_tail *t;
+
+       retval = __get_dirent_tail(fs, dirent, &t, 1);
+       if (retval)
+               return 1;
+
+       /*
+        * The checksum field is overlaid with the dirent->name field
+        * so the swapfs.c functions won't change the endianness.
+        */
+       retval = ext2fs_dirent_csum(fs, inum, dirent, &calculated,
+                                   (char *)t - (char *)dirent);
+       if (retval)
+               return 0;
+       return ext2fs_le32_to_cpu(t->det_checksum) == calculated;
+}
+
+static errcode_t ext2fs_dirent_csum_set(ext2_filsys fs, ext2_ino_t inum,
+                                       struct ext2_dir_entry *dirent)
+{
+       errcode_t retval;
+       __u32 crc;
+       struct ext2_dir_entry_tail *t;
+
+       retval = __get_dirent_tail(fs, dirent, &t, 1);
+       if (retval)
+               return retval;
+
+       /* swapfs.c functions don't change the checksum endianness */
+       retval = ext2fs_dirent_csum(fs, inum, dirent, &crc,
+                                   (char *)t - (char *)dirent);
+       if (retval)
+               return retval;
+       t->det_checksum = ext2fs_cpu_to_le32(crc);
+       return 0;
+}
+
 static errcode_t ext2fs_dx_csum(ext2_filsys fs, ext2_ino_t inum,
                                struct ext2_dir_entry *dirent,
                                __u32 *crc, int count_offset, int count,
@@ -179,12 +446,42 @@ static errcode_t ext2fs_dx_csum_set(ext2_filsys fs, ext2_ino_t inum,
        return retval;
 }
 
+int ext2fs_dir_block_csum_verify(ext2_filsys fs, ext2_ino_t inum,
+                                struct ext2_dir_entry *dirent)
+{
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 1;
+
+       if (__get_dirent_tail(fs, dirent, NULL, 1) == 0)
+               return ext2fs_dirent_csum_verify(fs, inum, dirent);
+       if (__get_dx_countlimit(fs, dirent, NULL, NULL, 1) == 0)
+               return ext2fs_dx_csum_verify(fs, inum, dirent);
+
+       return 0;
+}
+
+errcode_t ext2fs_dir_block_csum_set(ext2_filsys fs, ext2_ino_t inum,
+                                   struct ext2_dir_entry *dirent)
+{
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
+               return 0;
+
+       if (__get_dirent_tail(fs, dirent, NULL, 1) == 0)
+               return ext2fs_dirent_csum_set(fs, inum, dirent);
+       if (__get_dx_countlimit(fs, dirent, NULL, NULL, 1) == 0)
+               return ext2fs_dx_csum_set(fs, inum, dirent);
+
+       if (fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS)
+               return 0;
+       return EXT2_ET_DIR_NO_SPACE_FOR_CSUM;
+}
+
 #define EXT3_EXTENT_TAIL_OFFSET(hdr)   (sizeof(struct ext3_extent_header) + \
        (sizeof(struct ext3_extent) * ext2fs_le16_to_cpu((hdr)->eh_max)))
 
 static struct ext3_extent_tail *get_extent_tail(struct ext3_extent_header *h)
 {
-       return (struct ext3_extent_tail *)(((void *)h) +
+       return (struct ext3_extent_tail *)(((char *)h) +
                                           EXT3_EXTENT_TAIL_OFFSET(h));
 }
 
@@ -224,8 +521,7 @@ int ext2fs_extent_block_csum_verify(ext2_filsys fs, ext2_ino_t inum,
         * The extent tree structures are accessed in LE order, so we must
         * swap the checksum bytes here.
         */
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 1;
 
        provided = ext2fs_le32_to_cpu(t->et_checksum);
@@ -243,8 +539,7 @@ errcode_t ext2fs_extent_block_csum_set(ext2_filsys fs, ext2_ino_t inum,
        __u32 crc;
        struct ext3_extent_tail *t = get_extent_tail(eh);
 
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 0;
 
        /*
@@ -265,13 +560,12 @@ int ext2fs_inode_bitmap_csum_verify(ext2_filsys fs, dgrp_t group,
                        ext2fs_group_desc(fs, fs->group_desc, group);
        __u32 provided, calculated;
 
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 1;
        provided = gdp->bg_inode_bitmap_csum_lo;
        calculated = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)bitmap,
                                      size);
-       if (fs->super->s_desc_size >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
+       if (EXT2_DESC_SIZE(fs->super) >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
                provided |= (__u32)gdp->bg_inode_bitmap_csum_hi << 16;
        else
                calculated &= 0xFFFF;
@@ -286,13 +580,12 @@ errcode_t ext2fs_inode_bitmap_csum_set(ext2_filsys fs, dgrp_t group,
        struct ext4_group_desc *gdp = (struct ext4_group_desc *)
                        ext2fs_group_desc(fs, fs->group_desc, group);
 
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 0;
 
        crc = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)bitmap, size);
        gdp->bg_inode_bitmap_csum_lo = crc & 0xFFFF;
-       if (fs->super->s_desc_size >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
+       if (EXT2_DESC_SIZE(fs->super) >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
                gdp->bg_inode_bitmap_csum_hi = crc >> 16;
 
        return 0;
@@ -305,13 +598,12 @@ int ext2fs_block_bitmap_csum_verify(ext2_filsys fs, dgrp_t group,
                        ext2fs_group_desc(fs, fs->group_desc, group);
        __u32 provided, calculated;
 
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 1;
        provided = gdp->bg_block_bitmap_csum_lo;
        calculated = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)bitmap,
                                      size);
-       if (fs->super->s_desc_size >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_LOCATION)
+       if (EXT2_DESC_SIZE(fs->super) >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_LOCATION)
                provided |= (__u32)gdp->bg_block_bitmap_csum_hi << 16;
        else
                calculated &= 0xFFFF;
@@ -326,13 +618,12 @@ errcode_t ext2fs_block_bitmap_csum_set(ext2_filsys fs, dgrp_t group,
        struct ext4_group_desc *gdp = (struct ext4_group_desc *)
                        ext2fs_group_desc(fs, fs->group_desc, group);
 
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 0;
 
        crc = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)bitmap, size);
        gdp->bg_block_bitmap_csum_lo = crc & 0xFFFF;
-       if (fs->super->s_desc_size >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_LOCATION)
+       if (EXT2_DESC_SIZE(fs->super) >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_LOCATION)
                gdp->bg_block_bitmap_csum_hi = crc >> 16;
 
        return 0;
@@ -344,7 +635,7 @@ static errcode_t ext2fs_inode_csum(ext2_filsys fs, ext2_ino_t inum,
 {
        __u32 gen;
        struct ext2_inode_large *desc = inode;
-       size_t size = fs->super->s_inode_size;
+       size_t size = EXT2_INODE_SIZE(fs->super);
        __u16 old_lo;
        __u16 old_hi = 0;
 
@@ -373,11 +664,10 @@ int ext2fs_inode_csum_verify(ext2_filsys fs, ext2_ino_t inum,
 {
        errcode_t retval;
        __u32 provided, calculated;
-       int has_hi;
+       unsigned int i, has_hi;
+       char *cp;
 
-       if (fs->super->s_creator_os != EXT2_OS_LINUX ||
-           !EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 1;
 
        has_hi = (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE &&
@@ -393,7 +683,23 @@ int ext2fs_inode_csum_verify(ext2_filsys fs, ext2_ino_t inum,
        } else
                calculated &= 0xFFFF;
 
-       return provided == calculated;
+       if (provided == calculated)
+               return 1;
+
+       /*
+        * If the checksum didn't match, it's possible it was due to
+        * the inode being all zero's.  It's unlikely this is the
+        * case, but it can happen.  So check for it here.  (We only
+        * check the base inode since that's good enough, and it's not
+        * worth the bother to figure out how much of the extended
+        * inode, if any, is present.)
+        */
+       for (cp = (char *) inode, i = 0;
+            i < sizeof(struct ext2_inode);
+            cp++, i++)
+               if (*cp)
+                       return 0;
+       return 1;               /* Inode must have been all zero's */
 }
 
 errcode_t ext2fs_inode_csum_set(ext2_filsys fs, ext2_ino_t inum,
@@ -403,9 +709,7 @@ errcode_t ext2fs_inode_csum_set(ext2_filsys fs, ext2_ino_t inum,
        __u32 crc;
        int has_hi;
 
-       if (fs->super->s_creator_os != EXT2_OS_LINUX ||
-           !EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+       if (!ext2fs_has_feature_metadata_csum(fs->super))
                return 0;
 
        has_hi = (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE &&
@@ -422,53 +726,77 @@ errcode_t ext2fs_inode_csum_set(ext2_filsys fs, ext2_ino_t inum,
 
 __u16 ext2fs_group_desc_csum(ext2_filsys fs, dgrp_t group)
 {
+       struct ext2_group_desc *desc = ext2fs_group_desc(fs, fs->group_desc,
+                                                        group);
+       size_t offset, size = EXT2_DESC_SIZE(fs->super);
        __u16 crc = 0;
-       struct ext2_group_desc *desc;
-       size_t size;
-
-       size = fs->super->s_desc_size;
-       if (size < EXT2_MIN_DESC_SIZE)
-               size = EXT2_MIN_DESC_SIZE;
-       if (size > sizeof(struct ext4_group_desc)) {
-               /* This should never happen, but cap it for safety's sake */
-               size = sizeof(struct ext4_group_desc);
-       }
-
-       desc = ext2fs_group_desc(fs, fs->group_desc, group);
-
-       if (fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_GDT_CSUM) {
-               size_t offset = offsetof(struct ext2_group_desc, bg_checksum);
-
 #ifdef WORDS_BIGENDIAN
-               struct ext4_group_desc swabdesc;
+       struct ext4_group_desc swabdesc;
+       size_t save_size = size;
+       const size_t ext4_bg_size = sizeof(struct ext4_group_desc);
+       struct ext2_group_desc *save_desc = desc;
 
-               /* Have to swab back to little-endian to do the checksum */
-               memcpy(&swabdesc, desc, size);
-               ext2fs_swap_group_desc2(fs,
-                                       (struct ext2_group_desc *) &swabdesc);
-               desc = (struct ext2_group_desc *) &swabdesc;
+       /* Have to swab back to little-endian to do the checksum */
+       if (size > ext4_bg_size)
+               size = ext4_bg_size;
+       memcpy(&swabdesc, desc, size);
+       ext2fs_swap_group_desc2(fs, (struct ext2_group_desc *) &swabdesc);
+       desc = (struct ext2_group_desc *) &swabdesc;
+       group = ext2fs_swab32(group);
+#endif
 
-               group = ext2fs_swab32(group);
+       if (ext2fs_has_feature_metadata_csum(fs->super)) {
+               /* new metadata csum code */
+               __u16 old_crc;
+               __u32 crc32;
+
+               old_crc = desc->bg_checksum;
+               desc->bg_checksum = 0;
+               crc32 = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)&group,
+                                        sizeof(group));
+               crc32 = ext2fs_crc32c_le(crc32, (unsigned char *)desc,
+                                        size);
+               desc->bg_checksum = old_crc;
+#ifdef WORDS_BIGENDIAN
+               if (save_size > ext4_bg_size)
+                       crc32 = ext2fs_crc32c_le(crc32,
+                                    (unsigned char *)save_desc + ext4_bg_size,
+                                    save_size - ext4_bg_size);
 #endif
-               crc = ext2fs_crc16(~0, fs->super->s_uuid,
-                                  sizeof(fs->super->s_uuid));
-               crc = ext2fs_crc16(crc, &group, sizeof(group));
-               crc = ext2fs_crc16(crc, desc, offset);
-               offset += sizeof(desc->bg_checksum); /* skip checksum */
-               /* for checksum of struct ext4_group_desc do the rest...*/
-               if (offset < size) {
-                       crc = ext2fs_crc16(crc, (char *)desc + offset,
-                                          size - offset);
-               }
+               crc = crc32 & 0xFFFF;
+               goto out;
        }
 
+       /* old crc16 code */
+       offset = offsetof(struct ext2_group_desc, bg_checksum);
+       crc = ext2fs_crc16(~0, fs->super->s_uuid,
+                          sizeof(fs->super->s_uuid));
+       crc = ext2fs_crc16(crc, &group, sizeof(group));
+       crc = ext2fs_crc16(crc, desc, offset);
+       offset += sizeof(desc->bg_checksum); /* skip checksum */
+       /* for checksum of struct ext4_group_desc do the rest...*/
+       if (offset < size) {
+               crc = ext2fs_crc16(crc, (char *)desc + offset,
+                                  size - offset);
+       }
+#ifdef WORDS_BIGENDIAN
+       /*
+        * If the size of the bg descriptor is greater than 64
+        * bytes, which is the size of the traditional ext4 bg
+        * descriptor, checksum the rest of the descriptor here
+        */
+       if (save_size > ext4_bg_size)
+               crc = ext2fs_crc16(crc, (char *)save_desc + ext4_bg_size,
+                                  save_size - ext4_bg_size);
+#endif
+
+out:
        return crc;
 }
 
 int ext2fs_group_desc_csum_verify(ext2_filsys fs, dgrp_t group)
 {
-       if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                      EXT4_FEATURE_RO_COMPAT_GDT_CSUM) &&
+       if (ext2fs_has_group_desc_csum(fs) &&
            (ext2fs_bg_checksum(fs, group) !=
             ext2fs_group_desc_csum(fs, group)))
                return 0;
@@ -478,8 +806,7 @@ int ext2fs_group_desc_csum_verify(ext2_filsys fs, dgrp_t group)
 
 void ext2fs_group_desc_csum_set(ext2_filsys fs, dgrp_t group)
 {
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_GDT_CSUM))
+       if (!ext2fs_has_group_desc_csum(fs))
                return;
 
        /* ext2fs_bg_checksum_set() sets the actual checksum field but
@@ -513,8 +840,7 @@ errcode_t ext2fs_set_gdt_csum(ext2_filsys fs)
        if (!fs->inode_map)
                return EXT2_ET_NO_INODE_BITMAP;
 
-       if (!EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
-                                       EXT4_FEATURE_RO_COMPAT_GDT_CSUM))
+       if (!ext2fs_has_group_desc_csum(fs))
                return 0;
 
        for (i = 0; i < fs->group_desc_count; i++) {
@@ -522,6 +848,11 @@ errcode_t ext2fs_set_gdt_csum(ext2_filsys fs)
                __u32 old_unused = ext2fs_bg_itable_unused(fs, i);
                __u32 old_flags = ext2fs_bg_flags(fs, i);
                __u32 old_free_inodes_count = ext2fs_bg_free_inodes_count(fs, i);
+               __u32 old_free_blocks_count = ext2fs_bg_free_blocks_count(fs, i);
+
+               if (old_free_blocks_count == sb->s_blocks_per_group &&
+                   i != fs->group_desc_count - 1)
+                       ext2fs_bg_flags_set(fs, i, EXT2_BG_BLOCK_UNINIT);
 
                if (old_free_inodes_count == sb->s_inodes_per_group) {
                        ext2fs_bg_flags_set(fs, i, EXT2_BG_INODE_UNINIT);
@@ -556,21 +887,22 @@ void print_csum(const char *msg, ext2_filsys fs, dgrp_t group)
 {
        __u16 crc1, crc2, crc3;
        dgrp_t swabgroup;
-       struct ext2_group_desc *desc = ext2fs_group_desc(fs, fs->group_desc, group);
-       size_t size;
+       struct ext2_group_desc *desc = ext2fs_group_desc(fs, fs->group_desc,
+                                                        group);
+       size_t size = EXT2_DESC_SIZE(fs->super);
        struct ext2_super_block *sb = fs->super;
        int offset = offsetof(struct ext2_group_desc, bg_checksum);
 #ifdef WORDS_BIGENDIAN
        struct ext4_group_desc swabdesc;
+       struct ext2_group_desc *save_desc = desc;
+       const size_t ext4_bg_size = sizeof(struct ext4_group_desc);
+       size_t save_size = size;
 #endif
 
-       size = fs->super->s_desc_size;
-       if (size < EXT2_MIN_DESC_SIZE)
-               size = EXT2_MIN_DESC_SIZE;
-       if (size > sizeof(struct ext4_group_desc))
-               size = sizeof(struct ext4_group_desc);
 #ifdef WORDS_BIGENDIAN
        /* Have to swab back to little-endian to do the checksum */
+       if (size > ext4_bg_size)
+               size = ext4_bg_size;
        memcpy(&swabdesc, desc, size);
        ext2fs_swap_group_desc2(fs, (struct ext2_group_desc *) &swabdesc);
        desc = (struct ext2_group_desc *) &swabdesc;
@@ -587,8 +919,13 @@ void print_csum(const char *msg, ext2_filsys fs, dgrp_t group)
        /* for checksum of struct ext4_group_desc do the rest...*/
        if (offset < size)
                crc3 = ext2fs_crc16(crc3, (char *)desc + offset, size - offset);
+#ifdef WORDS_BIGENDIAN
+       if (save_size > ext4_bg_size)
+               crc3 = ext2fs_crc16(crc3, (char *)save_desc + ext4_bg_size,
+                                   save_size - ext4_bg_size);
+#endif
 
-       printf("%s: UUID %s(%04x), grp %u(%04x): %04x=%04x\n",
+       printf("%s UUID %s=%04x, grp %u=%04x: %04x=%04x\n",
               msg, e2p_uuid2str(sb->s_uuid), crc1, group, crc2, crc3,
               ext2fs_group_desc_csum(fs, group));
 }
@@ -606,6 +943,11 @@ int main(int argc, char **argv)
 
        memset(&param, 0, sizeof(param));
        ext2fs_blocks_count_set(&param, 32768);
+#if 0
+       param.s_feature_incompat |= EXT4_FEATURE_INCOMPAT_64BIT;
+       param.s_desc_size = 128;
+       csum_known = 0x5b6e;
+#endif
 
        retval = ext2fs_initialize("test fs", EXT2_FLAG_64BITS, &param,
                                   test_io_manager, &fs);
@@ -673,6 +1015,7 @@ int main(int argc, char **argv)
                printf("checksums for different data shouldn't match\n");
                exit(1);
        }
+       ext2fs_free(fs);
 
        return 0;
 }