Edit

thodg/ext4fs/ext4fs.c

Branch :

  • ext4fs.c
  • /* kc3
     * Copyright 2025 kmx.io <contact@kmx.io>
     *
     * Permission is hereby granted to use this software granted the above
     * copyright notice and this permission paragraph are included in all
     * copies and substantial portions of this software.
     *
     * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
     * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
     * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
     * THIS SOFTWARE.
     */
    #include "configure.h"
    #define _DEFAULT_SOURCE 1
    
    #if defined(OpenBSD)
    # include <sys/param.h>
    # include <sys/disklabel.h>
    # include <sys/dkio.h>
    #else
    # if defined(Linux)
    #  include <linux/fs.h>
    # endif
    #endif
    
    #include <assert.h>
    #include <endian.h>
    #include <err.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    #include <ext4fs.h>
    #include <crc32c.h>
    #include <uuid.h>
    
    const s_value_name ext4fs_bgd_flag_names[] = {
      {EXT4FS_BGD_FLAG_INODE_UNINIT, "inode-uninit"},
      {EXT4FS_BGD_FLAG_BLOCK_UNINIT, "block-uninit"},
      {EXT4FS_BGD_FLAG_INODE_ZEROED, "inode-zeroed"},
      {EXT4FS_BGD_FLAG_DIRTY,        "dirty"},
      {EXT4FS_BGD_FLAG_BLOCK_ZEROED, "block-zeroed"},
      {EXT4FS_BGD_FLAG_READ_ONLY,    "read-only"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_checksum_type_names[] = {
      {EXT4FS_CHECKSUM_TYPE_CRC32C, "crc32c"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_encoding_names[] = {
      {EXT4FS_ENCODING_UTF8, "utf8"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_encoding_flag_names[] = {
      {EXT4FS_ENCODING_FLAG_STRICT_MODE, "utf8"},
      {0, NULL}
    };
    
    const char *ext4fs_errors_names[] = {
      "0",
      "continue",
      "remount-ro",
      "panic",
      NULL
    };
    
    const s_value_name ext4fs_feature_compat_names[] = {
      {EXT4FS_FEATURE_COMPAT_DIR_PREALLOC,  "dir_prealloc"},
      {EXT4FS_FEATURE_COMPAT_IMAGIC_INODES, "imagic_inodes"},
      {EXT4FS_FEATURE_COMPAT_HAS_JOURNAL,   "has_journal"},
      {EXT4FS_FEATURE_COMPAT_EXT_ATTR,      "ext_attr"},
      {EXT4FS_FEATURE_COMPAT_RESIZE_INODE,  "resize_inode"},
      {EXT4FS_FEATURE_COMPAT_DIR_INDEX,     "dir_index"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_feature_incompat_names[] = {
      {EXT4FS_FEATURE_INCOMPAT_COMPRESSION, "compression"},
      {EXT4FS_FEATURE_INCOMPAT_FILETYPE,    "filetype"},
      {EXT4FS_FEATURE_INCOMPAT_RECOVER,     "recover"},
      {EXT4FS_FEATURE_INCOMPAT_JOURNAL_DEV, "journal_dev"},
      {EXT4FS_FEATURE_INCOMPAT_META_BG,     "meta_bg"},
      {EXT4FS_FEATURE_INCOMPAT_EXTENTS,     "extents"},
      {EXT4FS_FEATURE_INCOMPAT_64BIT,       "64bit"},
      {EXT4FS_FEATURE_INCOMPAT_MMP,         "mmp"},
      {EXT4FS_FEATURE_INCOMPAT_FLEX_BG,     "flex_bg"},
      {EXT4FS_FEATURE_INCOMPAT_EA_INODE,    "ea_inode"},
      {EXT4FS_FEATURE_INCOMPAT_DIRDATA,     "dirdata"},
      {EXT4FS_FEATURE_INCOMPAT_CSUM_SEED,   "csum_seed"},
      {EXT4FS_FEATURE_INCOMPAT_LARGEDIR,    "largedir"},
      {EXT4FS_FEATURE_INCOMPAT_INLINE_DATA, "inline_data"},
      {EXT4FS_FEATURE_INCOMPAT_ENCRYPT,     "encrypt"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_feature_ro_compat_names[] = {
      {EXT4FS_FEATURE_RO_COMPAT_SPARSE_SUPER,  "sparse_super"},
      {EXT4FS_FEATURE_RO_COMPAT_LARGE_FILE,    "large_file"},
      {EXT4FS_FEATURE_RO_COMPAT_BTREE_DIR,     "btree_dir"},
      {EXT4FS_FEATURE_RO_COMPAT_HUGE_FILE,     "huge_file"},
      {EXT4FS_FEATURE_RO_COMPAT_GDT_CSUM,      "gdt_csum"},
      {EXT4FS_FEATURE_RO_COMPAT_DIR_NLINK,     "dir_nlink"},
      {EXT4FS_FEATURE_RO_COMPAT_EXTRA_ISIZE,   "extra_isize"},
      {EXT4FS_FEATURE_RO_COMPAT_HAS_SNAPSHOT,  "has_snapshot"},
      {EXT4FS_FEATURE_RO_COMPAT_QUOTA,         "quota"},
      {EXT4FS_FEATURE_RO_COMPAT_BIGALLOC,      "bigalloc"},
      {EXT4FS_FEATURE_RO_COMPAT_METADATA_CSUM, "metadata_csum"},
      {EXT4FS_FEATURE_RO_COMPAT_REPLICA,       "replica"},
      {EXT4FS_FEATURE_RO_COMPAT_READONLY,      "readonly"},
      {EXT4FS_FEATURE_RO_COMPAT_PROJECT,       "project"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_flag_names[] = {
      {EXT4FS_FLAG_SIGNED_HASH, "signed_hash"},
      {EXT4FS_FLAG_UNSIGNED_HASH, "unsigned_hash"},
      {EXT4FS_FLAG_TEST_FILESYS, "test_fs"},
      {EXT4FS_FLAG_64BIT, "64bit"},
      {EXT4FS_FLAG_MOUNT_OPT_CHECK, "mount_opt_check"},
      {0, NULL}
    };
    
    const s_value_name ext4fs_inode_flag_names[] = {
      {EXTFS_INODE_FLAG_SECURE_RM                , "secure_rm"},
      {EXTFS_INODE_FLAG_UN_RM                    , "un_rm"},
      {EXTFS_INODE_FLAG_COMPRESSION              , "comp"},
      {EXTFS_INODE_FLAG_SYNC                     , "sync"},
      {EXTFS_INODE_FLAG_IMMUTABLE                , "immutable"},
      {EXTFS_INODE_FLAG_APPEND                   , "append"},
      {EXTFS_INODE_FLAG_NO_DUMP                  , "no_dump"},
      {EXTFS_INODE_FLAG_NO_ATIME                 , "no_atime"},
      {EXTFS_INODE_FLAG_DIRTY                    , "dirty"},
      {EXTFS_INODE_FLAG_COMPRESSED_BLOCKS        , "comp-blk"},
      {EXTFS_INODE_FLAG_NO_COMPRESSION           , "no-comp"},
      {EXTFS_INODE_FLAG_ENCRYPTED                , "encrypted"},
      {EXTFS_INODE_FLAG_INDEX                    , "index"},
      {EXTFS_INODE_FLAG_IMAGIC                   , "imagic"},
      {EXTFS_INODE_FLAG_JOURNAL_DATA             , "journal_data"},
      {EXTFS_INODE_FLAG_NO_TAIL                  , "no_tail"},
      {EXTFS_INODE_FLAG_DIR_SYNC                 , "dir_sync"},
      {EXTFS_INODE_FLAG_TOP_DIR                  , "top_dir"},
      {EXTFS_INODE_FLAG_HUGE_FILE                , "huge_file"},
      {EXTFS_INODE_FLAG_EXTENTS                  , "extents"},
      {EXTFS_INODE_FLAG_EXTENDED_ATTRIBUTES_INODE, "xattrs_inode"},
      {EXTFS_INODE_FLAG_EOF_BLOCKS               , "eof_blocks"},
      {EXTFS_INODE_FLAG_INLINE_DATA              , "inline_data"},
      {EXTFS_INODE_FLAG_PROJECT_ID_INHERITANCE   , "project_id_inheritance"},
      {EXTFS_INODE_FLAG_CASEFOLD                 , "casefold"}
    };
    
    const s_value_name ext4fs_mount_names[] = {
      {EXT4FS_MOUNT_READONLY,             "ro"},
      {EXT4FS_MOUNT_NO_ATIME,             "noatime"},
      {EXT4FS_MOUNT_DIRSYNC,              "dirsync"},
      {EXT4FS_MOUNT_DATA_JOURNAL,         "data=journal"},
      {EXT4FS_MOUNT_DATA_ORDERED,         "data=ordered"},
      {EXT4FS_MOUNT_DATA_WRITEBACK,       "data=writeback"},
      {EXT4FS_MOUNT_ERRORS_CONTINUE,      "errors=continue"},
      {EXT4FS_MOUNT_ERRORS_REMOUNT_RO,    "errors=remount-ro"},
      {EXT4FS_MOUNT_ERRORS_PANIC,         "errors=panic"},
      {EXT4FS_MOUNT_DISCARD,              "discard"},
      {EXT4FS_MOUNT_NO_BUFFER_HEADS,      "no-buffer-heads"},
      {EXT4FS_MOUNT_SKIP_JOURNAL,         "skip-journal"},
      {EXT4FS_MOUNT_NOAUTO_DELAYED_ALLOC, "noauto-delayed-alloc"},
      {0, NULL}
    };
    
    const char *ext4fs_os_names[] = {
      "Linux",
      "Hurd",
      "Masix",
      "FreeBSD",
      "Lites",
      "OpenBSD",
      NULL
    };
    
    const s_value_name ext4fs_state_names[] = {
      {EXT4FS_STATE_VALID, "valid"},
      {EXT4FS_STATE_ERROR, "error"},
      {0, NULL}
    };
    
    int ext4fs_64bit (const struct ext4fs_super_block *sb)
    {
      return (le32toh(sb->sb_feature_incompat) &
              EXT4FS_FEATURE_INCOMPAT_64BIT) ? 1 : 0;
    }
    
    int
    ext4fs_bgd_block_bitmap_block
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint64_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le32toh(bgd->bgd_block_bitmap_block_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(bgd->bgd_block_bitmap_block_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_bgd_block_bitmap_checksum
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le16toh(bgd->bgd_block_bitmap_checksum_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(bgd->bgd_block_bitmap_checksum_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_bgd_checksum_compute
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t block_group_id, uint16_t *dest)
    {
      uint32_t block_group_id_le;
      uint32_t crc;
      uint32_t seed;
      size_t size;
      struct ext4fs_block_group_descriptor tmp = {0};
      assert(sb);
      assert(bgd);
      assert(dest);
      if (! (sb->sb_feature_ro_compat & EXT4FS_FEATURE_RO_COMPAT_METADATA_CSUM)) {
        *dest = 0;
        return 0;
      }
      if (sb->sb_feature_incompat & EXT4FS_FEATURE_INCOMPAT_CSUM_SEED)
        seed = le32toh(sb->sb_checksum_seed);
      else {
        block_group_id_le = htole32(block_group_id);
        seed = crc32c(0, sb->sb_uuid, sizeof(sb->sb_uuid));
        seed = crc32c(seed, &block_group_id_le, sizeof(block_group_id_le));
      }
      if (ext4fs_64bit(sb))
        size = le16toh(sb->sb_block_group_descriptor_size);
      else
        size = 32;
      if (size > sizeof(tmp))
        return -1;
      memcpy(&tmp, bgd, size);
      tmp.bgd_checksum = 0;
      crc = crc32c(seed, &tmp, size);
      *dest = (~crc) & 0xFFFF;
      return 0;
    }
    
    int
    ext4fs_bgd_exclude_bitmap_block
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint64_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le32toh(bgd->bgd_exclude_bitmap_block_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(bgd->bgd_exclude_bitmap_block_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_bgd_free_blocks_count
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le16toh(bgd->bgd_free_blocks_count_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(bgd->bgd_free_blocks_count_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_bgd_free_inodes_count
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le16toh(bgd->bgd_free_inodes_count_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(bgd->bgd_free_inodes_count_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_bgd_inode_bitmap_block
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint64_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le32toh(bgd->bgd_inode_bitmap_block_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(bgd->bgd_inode_bitmap_block_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_bgd_inode_bitmap_checksum
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le16toh(bgd->bgd_inode_bitmap_checksum_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(bgd->bgd_inode_bitmap_checksum_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_bgd_inode_table_block
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint64_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le32toh(bgd->bgd_inode_table_block_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(bgd->bgd_inode_table_block_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_bgd_inode_table_unused
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le16toh(bgd->bgd_inode_table_unused_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(bgd->bgd_inode_table_unused_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_bgd_used_dirs_count
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t *dest)
    {
      assert(sb);
      assert(bgd);
      assert(dest);
      *dest = le16toh(bgd->bgd_used_dirs_count_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(bgd->bgd_used_dirs_count_hi) << 16;
      return 0;
    }
    
    struct ext4fs_block_group_descriptor *
    ext4fs_block_group_descriptor_read
    (const struct ext4fs_super_block *sb,
     struct ext4fs_block_group_descriptor *bgd,
     uint64_t bgd_count,
     int fd)
    {
      uint32_t block_size;
      ssize_t done;
      uint32_t offset;
      ssize_t r;
      ssize_t remaining;
      assert(sb);
      assert(bgd);
      if (ext4fs_sb_block_size(sb, &block_size))
        return NULL;
      offset = (EXT4FS_SUPER_BLOCK_OFFSET + EXT4FS_SUPER_BLOCK_SIZE +
                (block_size - 1)) / block_size * block_size;
      if (lseek(fd, offset, SEEK_SET) < 0) {
        warn("lseek %u", offset);
        return NULL;
      }
      done = 0;
      remaining = sizeof(struct ext4fs_block_group_descriptor) *
        bgd_count;
      while (remaining > 0) {
        r = read(fd, (char *) bgd + done, remaining);
        if (r < 0) {
          warn("read super block %ld", remaining);
          return NULL;
        }
        done += r;
        remaining -= r;
      }
      return bgd;
    }
    
    #ifdef OpenBSD
    
    struct disklabel *
    ext4fs_disklabel_get (struct disklabel *dl, int fd)
    {
      assert(dl);
      if (ioctl(fd, DIOCGDINFO, (char *) dl) == -1) {
        warn("disklabel_get: ioctl DIOCGDINFO");
        return NULL;
      }
      return dl;
    }
    
    #endif /* OpenBSD */
    
    int
    ext4fs_extent_start
    (const struct ext4fs_extent *extent,
     uint64_t *start)
    {
      assert(extent);
      assert(start);
      *start = le32toh(extent->e_start_lo);
      *start |= (uint64_t) le16toh(extent->e_start_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_inode_256_checksum_compute
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode_256 *inode_256,
     uint32_t inode_number,
     uint32_t *dest)
    {
      uint32_t crc;
      uint32_t inode_number_le;
      uint32_t seed;
      struct ext4fs_inode_256 tmp = {0};
      assert(sb);
      assert(inode_256);
      assert(dest);
      if (! (sb->sb_feature_ro_compat & EXT4FS_FEATURE_RO_COMPAT_METADATA_CSUM)) {
        *dest = 0;
        return 0;
      }
      if (sb->sb_feature_incompat & EXT4FS_FEATURE_INCOMPAT_CSUM_SEED)
        seed = le32toh(sb->sb_checksum_seed);
      else {
        seed = crc32c(0, sb->sb_uuid, sizeof(sb->sb_uuid));
      }
      inode_number_le = htole32(inode_number);
      crc = crc32c(seed, &inode_number_le, sizeof(inode_number_le));
      crc = crc32c(crc, &inode_256->inode.i_nfs_generation,
                   sizeof(inode_256->inode.i_nfs_generation));
      tmp = *inode_256;
      tmp.inode.i_checksum_lo = 0;
      tmp.inode.i_checksum_hi = 0;
      crc = crc32c(crc, &tmp, sizeof(tmp));
      *dest = ~crc;
      return 0;
    }
    
    struct ext4fs_inode_256 *
    ext4fs_inode_256_read
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd_table,
     struct ext4fs_inode_256 *inode_256, 
     uint32_t inode_number,
     int fd)
    {
      uint32_t block_group;
      uint32_t block_size;
      uint32_t inode_index;
      uint64_t inode_offset;
      uint16_t inode_size;
      uint64_t inode_table_block;
      ssize_t done;
      ssize_t r;
      ssize_t remaining;
      assert(sb);
      assert(bgd_table);
      assert(inode_256);
      if (inode_number < 1) {
        fprintf(stderr, "ext4fs_inode_256_read: invalid inode number %u\n",
                inode_number);
        return NULL;
      }
      block_group = (inode_number - 1) / le32toh(sb->sb_inodes_per_group);
      inode_index = (inode_number - 1) % le32toh(sb->sb_inodes_per_group);
      if (block_group >= ext4fs_sb_block_group_count(sb)) {
        fprintf(stderr, "ext4fs_inode_256_read: block group %u out of range\n",
                block_group);
        return NULL;
      }
      if (ext4fs_sb_block_size(sb, &block_size)) {
        fprintf(stderr, "ext4fs_inode_256_read: ext4fs_sb_block_size\n");
        return NULL;
      }
      if (ext4fs_bgd_inode_table_block(sb, &bgd_table[block_group],
                                       &inode_table_block)) {
        fprintf(stderr,
                "ext4fs_inode_256_read: ext4fs_bgd_inode_table_block\n");
        return NULL;
      }
      if (ext4fs_sb_inode_size(sb, &inode_size))
        return NULL;
      inode_offset = inode_table_block * block_size +
        inode_index * inode_size;
      if (lseek(fd, inode_offset, SEEK_SET) < 0) {
        warn("ext4fs_inode_256_read: lseek " CONFIGURE_FMT_UINT64,
             inode_offset);
        return NULL;
      }
      if (sizeof(struct ext4fs_inode_256) != inode_size) {
        fprintf(stderr, "ext4fs_inode_256_read: invalid inode size: %u",
                inode_size);
        return NULL;
      }
      done = 0;
      remaining = inode_size;
      while (remaining > 0) {
        r = read(fd, (char *) inode_256 + done, remaining);
        if (r < 0) {
          warn("ext4fs_inode_256_read: read inode %ld", remaining);
          return NULL;
        }
        done += r;
        remaining -= r;
      }
      return inode_256;
    }
    
    int
    ext4fs_inode_blocks
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode *inode,
     uint64_t *dest)
    {
      assert(sb);
      assert(inode);
      assert(dest);
      *dest = le32toh(inode->i_blocks_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le16toh(inode->i_blocks_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_inode_checksum
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode *inode,
     uint32_t *dest)
    {
      assert(sb);
      assert(inode);
      assert(dest);
      *dest = le16toh(inode->i_checksum_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(inode->i_checksum_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_inode_extended_attributes
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode *inode,
     uint64_t *dest)
    {
      assert(inode);
      assert(dest);
      *dest = le32toh(inode->i_extended_attributes_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le16toh(inode->i_extended_attributes_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_inode_gid
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode *inode,
     uint32_t *dest)
    {
      assert(sb);
      assert(inode);
      assert(dest);
      *dest = le16toh(inode->i_gid_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(inode->i_gid_hi) << 16;
      return 0;
    }
    
    int
    ext4fs_inode_size
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode *inode,
     uint64_t *dest)
    {
      assert(sb);
      assert(inode);
      assert(dest);
      *dest = le32toh(inode->i_size_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(inode->i_size_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_inode_uid
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_inode *inode,
     uint32_t *dest)
    {
      assert(sb);
      assert(inode);
      assert(dest);
      *dest = le16toh(inode->i_uid_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint32_t) le16toh(inode->i_uid_hi) << 16;
      return 0;
    }
    
    int ext4fs_inspect (const char *dev, int fd)
    {
      struct ext4fs_block_group_descriptor *bgd = NULL;
      uint64_t                              bgd_count = 0;
      uint64_t i;
      uint32_t inode_number = 0;
      struct ext4fs_inode_256 inode_256 = {0};
      struct ext4fs_super_block sb = {0};
      uint16_t                  sb_inode_size;
      uint64_t size = 0;
      assert(dev);
      if (ext4fs_size(dev, fd, &size) ||
          ! size)
        return -1;
      printf("ext4fs_size: " CONFIGURE_FMT_UINT64 "\n", size);
      if (! ext4fs_super_block_read(&sb, fd))
        return -1;
      if (ext4fs_inspect_super_block(&sb))
        return -1;
      if (! (bgd_count = ext4fs_sb_block_group_count(&sb)))
        return -1;
      printf("ext4fs_block_group_count: " CONFIGURE_FMT_UINT64 "\n",
             bgd_count);
      bgd = calloc(bgd_count, sizeof(struct ext4fs_block_group_descriptor));
      if (! bgd)
        return -1;
      if (! ext4fs_block_group_descriptor_read(&sb, bgd, bgd_count, fd))
        return -1;
      i = 0;
      while (i < bgd_count) {
        printf("# " CONFIGURE_FMT_UINT64 "\n", i);
        if (ext4fs_inspect_block_group_descriptor(&sb, bgd + i, i)) {
          fprintf(stderr, "ext4fs_inspect:"
                  " ext4fs_inspect_block_group_descriptor\n");
          return -1;
        }
        i++;
      }
      inode_number = 2;
      if (ext4fs_sb_inode_size(&sb, &sb_inode_size))
        return -1;
      if (sb_inode_size == 256) {
        if (! ext4fs_inode_256_read(&sb, bgd, &inode_256, inode_number, fd)) {
          fprintf(stderr, "ext4fs_inspect: ext4fs_inode_256_read\n");
          return -1;
        }
        printf("# %u\n", inode_number);
        if (ext4fs_inspect_inode_256(&sb, &inode_256, inode_number)) {
          fprintf(stderr, "ext4fs_inspect: ext4fs_inspect_inode_256\n");
          return -1;
        }
      }
      printf("\n"
             "EOF\n");
      return 0;
    }
    
    int ext4fs_inspect_inode_256 (const struct ext4fs_super_block *sb,
                                  const struct ext4fs_inode_256 *inode_256,
                                  uint32_t inode_number)
    {
      uint32_t atime;
      char     atime_str[32] = {0};
      uint64_t blocks;
      uint32_t checksum;
      uint32_t checksum_computed;
      uint32_t crtime;
      char     crtime_str[32] = {0};
      uint32_t ctime;
      char     ctime_str[32] = {0};
      struct ext4fs_extent_header eh = {0};
      uint64_t extended_attributes;
      struct ext4fs_extent extent = {0};
      int      i;
      char     mode_str[14] = {0};
      uint32_t mtime;
      char     mtime_str[32] = {0};
      uint32_t dtime;
      char     dtime_str[32] = {0};
      uint32_t gid;
      uint16_t mode;
      uint64_t size;
      uint32_t uid;
      assert(sb);
      assert(inode_256);
      mode = le16toh(inode_256->inode.i_mode);
      atime = le32toh(inode_256->inode.i_atime);
      ctime = le32toh(inode_256->inode.i_ctime);
      mtime = le32toh(inode_256->inode.i_mtime);
      dtime = le32toh(inode_256->inode.i_dtime);
      crtime = le32toh(inode_256->inode.i_crtime);
      if (ext4fs_mode_to_str(mode, mode_str, sizeof(mode_str)) ||
          ext4fs_inode_uid(sb, &inode_256->inode, &uid) ||
          ext4fs_inode_size(sb, &inode_256->inode, &size) ||
          ext4fs_time_to_str(atime, atime_str, sizeof(atime_str)) ||
          ext4fs_time_to_str(ctime, ctime_str, sizeof(ctime_str)) ||
          ext4fs_time_to_str(mtime, mtime_str, sizeof(mtime_str)) ||
          ext4fs_time_to_str(dtime, dtime_str, sizeof(dtime_str)) ||
          ext4fs_inode_gid(sb, &inode_256->inode, &gid) ||
          ext4fs_inode_blocks(sb, &inode_256->inode, &blocks) ||
          ext4fs_inode_extended_attributes(sb, &inode_256->inode,
                                           &extended_attributes) ||
          ext4fs_inode_checksum(sb, &inode_256->inode, &checksum) ||
          ext4fs_inode_256_checksum_compute(sb, inode_256, inode_number,
                                            &checksum_computed) ||
          ext4fs_time_to_str(crtime, crtime_str, sizeof(crtime_str)))
        return -1;
      printf("%%Ext4fs.Inode256{i_mode: (U16) %u,\t# %s\n"
             "                 i_uid: (U32) %u,\n"
             "                 i_size: (U64) " CONFIGURE_FMT_UINT64 ",\n"
             "                 i_atime: %%Time{tv_sec: %u,\t# %s\n"
             "                                tv_nsec: %u},\n"
             "                 i_ctime: %%Time{tv_sec: %u,\t# %s\n"
             "                                tv_nsec: %u},\n"
             "                 i_mtime: %%Time{tv_sec: %u,\t# %s\n"
             "                                tv_nsec: %u},\n"
             "                 i_dtime: (U32) %u,\t# %s\n"
             "                 i_gid: (U32) %u,\n"
             "                 i_links_count: (U16) %u,\n"
             "                 i_blocks: (U64) " CONFIGURE_FMT_UINT64 ",\n"
             "                 i_flags: ",
             mode, mode_str,
             uid,
             size,
             atime, atime_str, le32toh(inode_256->inode.i_atime_extra),
             ctime, ctime_str, le32toh(inode_256->inode.i_ctime_extra),
             mtime, mtime_str, le32toh(inode_256->inode.i_mtime_extra),
             dtime, dtime_str,
             gid,
             le16toh(inode_256->inode.i_links_count),
             blocks);
      if (ext4fs_inspect_flag_names(le32toh(inode_256->inode.i_flags),
                                    ext4fs_inode_flag_names))
        return -1;
      printf(",\n"
             "                 i_version: (U32) %u,\n",
             le32toh(inode_256->inode.i_version));
      printf("                 i_extent_header:\n");
      eh = inode_256->inode.i_extent_header;
      ext4fs_inspect_extent_header(&eh, 19);
      printf(",\n");
      if (! eh.eh_depth) {
        printf("                 i_extent: {\t# eh_depth == 0\n");
        i = 0;
        while (i < eh.eh_entries) {
          printf("                   # %i\n",
                 i);
          extent = inode_256->inode.i_extent[i];
          if (ext4fs_inspect_extent(&extent, 19))
            return -1;
          printf(",\n");
          i++;
        }
      }
      printf("                 },\n"
             "                 i_nfs_generation: (U32) %u,\n"
             "                 i_extended_attributes: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                 i_fragment_address: (U32) %u,\n"
             "                 i_checksum: (U32) 0x%08X, # 0x%08X\n"
             "                 i_extra_isize: (U16) %u,\n"
             "                 i_crtime: %%Time{tv_sec: %u,\t# %s\n"
             "                                 tv_nsec: %u},\n"
             "                 i_project_id: (U32) %u}",
             le32toh(inode_256->inode.i_nfs_generation),
             extended_attributes,
             le32toh(inode_256->inode.i_fragment_address),
             checksum, checksum_computed,
             le16toh(inode_256->inode.i_extra_isize),
             crtime, crtime_str,
             le32toh(inode_256->inode.i_crtime_extra),
             le32toh(inode_256->inode.i_project_id));
      return 0;
    }
    
    int ext4fs_inspect_extent (const struct ext4fs_extent *extent,
                               uint8_t indent)
    {
      char s[80] = {0};
      uint64_t start;
      assert(extent);
      if (indent >= sizeof(s))
        return -1;
      memset(s, ' ', indent);
      if (ext4fs_extent_start(extent, &start))
        return -1;
      printf("%s%%Ext4fs.Extent{e_block: (U32) %u,\n"
             "%s               e_len: (U16) %u,\n"
             "%s               e_start: (U64) " CONFIGURE_FMT_UINT64 "}",
             s, le32toh(extent->e_block),
             s, le16toh(extent->e_len),
             s, start);
      return 0;
    }
    
    int ext4fs_inspect_extent_header (const struct ext4fs_extent_header *eh,
                                      uint8_t indent)
    {
      char s[80] = {0};
      assert(eh);
      if (indent >= sizeof(s))
        return -1;
      memset(s, ' ', indent);
      printf("%s%%Ext4fs.ExtentHeader{eh_magic: (U16) 0x%04X, # 0xF30A\n"
             "%s                     eh_entries: (U16) %u,\n"
             "%s                     eh_max: (U16) %u,\n"
             "%s                     eh_depth: (U16) %u,\n"
             "%s                     eh_generation: (U32) %u}",
             s, le16toh(eh->eh_magic),
             s, le16toh(eh->eh_entries),
             s, le16toh(eh->eh_max),
             s, le16toh(eh->eh_depth),
             s, le32toh(eh->eh_generation));
      return 0;
    }
    
    int ext4fs_mode_to_str (uint16_t mode, char *dest, size_t size)
    {
      assert(dest);
      if (size < 14)
        return -1;
      memset(dest, 0, 14);
      if (S_ISREG(mode))
        dest[0] = '-';
      else if (S_ISDIR(mode))
        dest[0] = 'd';
      else if (S_ISLNK(mode))
        dest[0] = 'l';
      else if (S_ISCHR(mode))
        dest[0] = 'c';
      else if (S_ISBLK(mode))
        dest[0] = 'b';
      else if (S_ISFIFO(mode))
        dest[0] = 'f';
      else if (S_ISSOCK(mode))
        dest[0] = 's';
      dest[1]  = (S_ISUID & mode) ? 'U' : '-';
      dest[2]  = (S_ISGID & mode) ? 'G' : '-';
      dest[3]  = (S_ISVTX & mode) ? 'v' : '-';
      dest[4]  = (S_IRUSR & mode) ? 'r' : '-';
      dest[5]  = (S_IWUSR & mode) ? 'w' : '-';
      dest[6]  = (S_IXUSR & mode) ? 'x' : '-';
      dest[7]  = (S_IRGRP & mode) ? 'r' : '-';
      dest[8]  = (S_IWGRP & mode) ? 'w' : '-';
      dest[9]  = (S_IXGRP & mode) ? 'x' : '-';
      dest[10] = (S_IROTH & mode) ? 'r' : '-';
      dest[11] = (S_IWOTH & mode) ? 'w' : '-';
      dest[12] = (S_IXOTH & mode) ? 'x' : '-';
      return 0;
    }
    
    void ext4fs_inspect_os (uint32_t os)
    {
      if (os < sizeof(ext4fs_os_names) / sizeof(const char *) - 1)
        printf("%s", ext4fs_os_names[os]);
      else
        printf("(U32) %u", os);
    }
    
    int ext4fs_inspect_flag_names (uint32_t flags,
                                   const s_value_name *names)
    {
      char first;
      const s_value_name *i;
      uint32_t remaining;
      assert(names);
      first = 1;
      i = names;
      remaining = flags;
      while (i->name) {
        if (flags & i->value) {
          if (! first)
            printf(" | ");
          else
            first = 0;
          printf("%s", i->name);
          remaining &= ~i->value;
        }
        i++;
      }
      if (first)
        printf("0x%04X", remaining);
      else if (remaining)
        printf(" | 0x%04X", remaining);
      return 0;
    }
    
    void ext4fs_inspect_errors (uint16_t errors)
    {
      if (errors < sizeof(ext4fs_errors_names) / sizeof(const char *) - 1)
        printf("%s", ext4fs_errors_names[errors]);
      else
        printf("(U16) %u", errors);
    }
        
    int
    ext4fs_inspect_block_group_descriptor
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd,
     uint32_t block_group_id)
    {
      uint64_t block_bitmap_block;
      uint32_t block_bitmap_checksum;
      uint16_t checksum = 0;
      uint16_t checksum_computed;
      uint64_t exclude_bitmap_block;
      uint32_t free_blocks_count;
      uint32_t free_inodes_count;
      uint64_t inode_bitmap_block;
      uint32_t inode_bitmap_checksum;
      uint64_t inode_table_block;
      uint32_t inode_table_unused;
      uint32_t used_dirs_count;
      assert(sb);
      assert(bgd);
      if (ext4fs_bgd_block_bitmap_block(sb, bgd, &block_bitmap_block) ||
          ext4fs_bgd_inode_bitmap_block(sb, bgd, &inode_bitmap_block) ||
          ext4fs_bgd_inode_table_block(sb, bgd, &inode_table_block) ||
          ext4fs_bgd_free_blocks_count(sb, bgd, &free_blocks_count) ||
          ext4fs_bgd_free_inodes_count(sb, bgd, &free_inodes_count) ||
          ext4fs_bgd_used_dirs_count(sb, bgd, &used_dirs_count) ||
          ext4fs_bgd_exclude_bitmap_block(sb, bgd, &exclude_bitmap_block) ||
          ext4fs_bgd_block_bitmap_checksum(sb, bgd,
                                           &block_bitmap_checksum) ||
          ext4fs_bgd_inode_bitmap_checksum(sb, bgd,
                                           &inode_bitmap_checksum) ||
          ext4fs_bgd_inode_table_unused(sb, bgd, &inode_table_unused) ||
          ext4fs_bgd_checksum_compute(sb, bgd, block_group_id,
                                      &checksum_computed))
        return -1;
      checksum = le16toh(bgd->bgd_checksum);
      printf("%%Ext4fs.BlockGroupDescriptor{bgd_block_bitmap_block: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                             bgd_inode_bitmap_block: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                             bgd_inode_table_block: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                             bgd_free_blocks_count: (U32) %u,\n"
             "                             bgd_free_inodes_count: (U32) %u,\n"
             "                             bgd_used_dirs_count: (U32) %u,\n"
             "                             bgd_flags: ",
             block_bitmap_block,
             inode_bitmap_block,
             inode_table_block,
             free_blocks_count,
             free_inodes_count,
             used_dirs_count);
      ext4fs_inspect_flag_names(bgd->bgd_flags, ext4fs_bgd_flag_names);
      printf(",\n"
             "                             bgd_exclude_bitmap_block: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                             bgd_block_bitmap_checksum: "
             "(U32) 0x%08X,\n"
             "                             bgd_inode_bitmap_checksum: "
             "(U32) 0x%08X,\n"
             "                             bgd_inode_table_unused: "
             "(U32) %u,\n"
             "                             bgd_checksum: (U16) 0x%04X} # 0x%04X\n",
             exclude_bitmap_block,
             block_bitmap_checksum,
             inode_bitmap_checksum,
             inode_table_unused,
             checksum, checksum_computed);
      return 0;
    }
    
    int
    ext4fs_inspect_block_group_descriptor_hex
    (const struct ext4fs_super_block *sb,
     const struct ext4fs_block_group_descriptor *bgd)
    {
      uint16_t i = 0;
      const uint8_t *p;
      uint16_t size;
      assert(sb);
      assert(bgd);
      p = (uint8_t *) bgd;
      size = le16toh(sb->sb_block_group_descriptor_size);
      while (i < size) {
        printf("%02X ", *p);
        if (i % 16 == 15)
          printf("\n");
        i++;
        p++;
      }
      return 0;
    }
    
    int ext4fs_inspect_super_block (const struct ext4fs_super_block *sb)
    {
      uint64_t blocks_count;
      uint64_t check_time;
      uint32_t checksum;
      uint32_t checksum_computed = 0;
      uint64_t first_error_time;
      char     first_error_function[EXT4FS_FUNCTION_MAX + 1] = {0};
      uint64_t free_blocks_count;
      char     last_error_function[EXT4FS_FUNCTION_MAX + 1] = {0};
      uint64_t last_error_time;
      char     last_mounted[EXT4FS_LAST_MOUNTED_MAX + 1] = {0};
      char     mount_opts[EXT4FS_MOUNT_OPTS_MAX + 1] = {0};
      uint64_t mount_time;
      uint64_t newfs_time;
      uint64_t reserved_blocks_count;
      char str_check_time[32] = {0};
      char str_mount_time[32] = {0};
      char str_write_time[32] = {0};
      char str_newfs_time[32] = {0};
      char str_first_error_time[32] = {0};
      char str_last_error_time[32] = {0};
      char volume_name[EXT4FS_VOLUME_NAME_MAX + 1] = {0};
      uint64_t write_time;
      assert(sb);
      checksum = le32toh(sb->sb_checksum);
      if (ext4fs_sb_blocks_count(sb, &blocks_count) ||
          ext4fs_sb_reserved_blocks_count(sb, &reserved_blocks_count) ||
          ext4fs_sb_free_blocks_count(sb, &free_blocks_count) ||
          ext4fs_sb_mount_time(sb, &mount_time) ||
          ext4fs_sb_write_time(sb, &write_time) ||
          ext4fs_sb_check_time(sb, &check_time) ||
          ext4fs_sb_newfs_time(sb, &newfs_time) ||
          ext4fs_sb_first_error_time(sb, &first_error_time) ||
          ext4fs_sb_last_error_time(sb, &last_error_time) ||
          ext4fs_time_to_str(mount_time, str_mount_time,
                             sizeof(str_mount_time)) ||
          ext4fs_time_to_str(write_time, str_write_time,
                             sizeof(str_write_time)) ||
          ext4fs_time_to_str(check_time, str_check_time,
                             sizeof(str_check_time)) ||
          ext4fs_time_to_str(newfs_time, str_newfs_time,
                             sizeof(str_newfs_time)) ||
          ext4fs_time_to_str(first_error_time, str_first_error_time,
                             sizeof(str_first_error_time)) ||
          ext4fs_time_to_str(last_error_time, str_last_error_time,
                             sizeof(str_last_error_time)) ||
          ext4fs_sb_checksum_compute(sb, &checksum_computed))
        return -1;
      strlcpy(volume_name, sb->sb_volume_name, sizeof(volume_name));
      strlcpy(last_mounted, sb->sb_last_mounted, sizeof(last_mounted));
      strlcpy(first_error_function, sb->sb_first_error_function,
              sizeof(first_error_function));
      strlcpy(last_error_function, sb->sb_last_error_function,
              sizeof(last_error_function));
      strlcpy(mount_opts, sb->sb_mount_opts,
              sizeof(mount_opts));
      printf("%%Ext4fs.SuperBlock{sb_inodes_count: (U32) %u,\n"
             "                   sb_blocks_count: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                   sb_reserved_blocks_count: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                   sb_free_blocks_count: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                   sb_free_inodes_count: (U32) %u,\n"
             "                   sb_first_data_block: (U32) %u,\n"
             "                   sb_log_block_size: (U32) %u,  \t# %u\n"
             "                   sb_log_cluster_size: (U32) %u,\t# %u\n"
             "                   sb_blocks_per_group: (U32) %u,\n"
             "                   sb_clusters_per_group: (U32) %u,\n"
             "                   sb_inodes_per_group: (U32) %u,\n"
             "                   sb_mount_time: (U64) "
             CONFIGURE_FMT_UINT64 ",\t# %s\n"
             "                   sb_write_time: (U64) "
             CONFIGURE_FMT_UINT64 ",\t# %s\n"
             "                   sb_mount_count: (U16) %u,\n"
             "                   sb_max_mount_count: (S16) %d,\n"
             "                   sb_magic: (U16) 0x%04X,\t\t# %u\n"
             "                   sb_state: ",
             le32toh(sb->sb_inodes_count),
             blocks_count, reserved_blocks_count, free_blocks_count,
             le32toh(sb->sb_free_inodes_count),
             le32toh(sb->sb_first_data_block),
             le32toh(sb->sb_log_block_size),
             (uint32_t) 1 << (le32toh(sb->sb_log_block_size) + 10), 
             le32toh(sb->sb_log_cluster_size),
             (uint32_t) 1 << (le32toh(sb->sb_log_cluster_size) + 10), 
             le32toh(sb->sb_blocks_per_group),
             le32toh(sb->sb_clusters_per_group),
             le32toh(sb->sb_inodes_per_group),
             mount_time, str_mount_time,
             write_time, str_write_time,
             le16toh(sb->sb_mount_count),
             (int16_t) le16toh(sb->sb_max_mount_count_before_fsck),
             le16toh(sb->sb_magic), le16toh(sb->sb_magic));
      ext4fs_inspect_flag_names(le16toh(sb->sb_state), ext4fs_state_names);
      printf(",\n"
             "                   sb_errors: ");
      ext4fs_inspect_errors(le16toh(sb->sb_errors));
      printf(",\n"
             "                   sb_revision_level_minor: (U16) %u,\n"
             "                   sb_check_time: (U64) "
             CONFIGURE_FMT_UINT64 ",\t# %s\n"
             "                   sb_check_interval: (U32) %u,\n"
             "                   sb_creator_os: (U32) %u,\t\t# ",
             le32toh(sb->sb_revision_level_minor),
             check_time, str_check_time,
             le32toh(sb->sb_check_interval),
             le32toh(sb->sb_creator_os));
      ext4fs_inspect_os(le32toh(sb->sb_creator_os));
      printf("\n"
             "                   sb_revision_level: (U32) %u,\n"
             "                   sb_default_reserved_uid: (U16) %u,\n"
             "                   sb_default_reserved_gid: (U16) %u,\n"
             "                   sb_feature_compat: ",
             le32toh(sb->sb_revision_level),
             le16toh(sb->sb_default_reserved_uid),
             le16toh(sb->sb_default_reserved_gid));
      ext4fs_inspect_flag_names(le32toh(sb->sb_feature_compat),
                                 ext4fs_feature_compat_names);
      printf(",\n"
             "                   sb_feature_incompat: ");
      ext4fs_inspect_flag_names(le32toh(sb->sb_feature_incompat),
                                 ext4fs_feature_incompat_names);
      printf(",\n"
             "                   sb_feature_ro_compat: ");
      ext4fs_inspect_flag_names(le32toh(sb->sb_feature_ro_compat),
                                 ext4fs_feature_ro_compat_names);
      printf(",\n"
             "                   sb_uuid: ");
      uuid_print(sb->sb_uuid);
      printf(",\n"
             "                   sb_volume_name: %s,\n"
             "                   sb_last_mounted: %s,\n"
             "                   sb_algorithm_usage_bitmap: (U32) %u,\n"
             "                   sb_preallocate_blocks: (U8) %u,\n"
             "                   sb_preallocate_dir_blocks: (U8) %u,\n"
             "                   sb_reserved_bgdt_blocks: (U16) %u,\n"
             "                   sb_journal_uuid: ",
             volume_name,
             last_mounted,
             le32toh(sb->sb_algorithm_usage_bitmap),
             sb->sb_preallocate_blocks,
             sb->sb_preallocate_dir_blocks,
             le16toh(sb->sb_reserved_bgdt_blocks));
      uuid_print(sb->sb_journal_uuid);
      printf(",\n"
             "                   sb_journal_inode_number: (U32) %u,\n"
             "                   sb_journal_device_number: (U32) %u,\n"
             "                   sb_last_orphan: (U32) %u,\n"
             "                   sb_hash_seed: (U32) {0x%08x,\n"
             "                                        0x%08x,\n"
             "                                        0x%08x,\n"
             "                                        0x%08x},\n"
             "                   sb_default_hash_version: %u,\n"
             "                   sb_journal_backup_type: %u,\n"
             "                   sb_block_group_descriptor_size: (U16) %u,\n"
             "                   sb_default_mount_opts: ",
             le32toh(sb->sb_journal_inode_number),
             le32toh(sb->sb_journal_device_number),
             le32toh(sb->sb_last_orphan),
             sb->sb_hash_seed[0], sb->sb_hash_seed[1], sb->sb_hash_seed[2],
             sb->sb_hash_seed[3],
             sb->sb_default_hash_version,
             sb->sb_journal_backup_type,
             le16toh(sb->sb_block_group_descriptor_size));
      ext4fs_inspect_flag_names(le32toh(sb->sb_default_mount_opts),
                                 ext4fs_mount_names);
      printf(",\n"
             "                   sb_first_meta_block_group: (U32) %u,\n"
             "                   sb_newfs_time: (U64) "
             CONFIGURE_FMT_UINT64 ",\t# %s\n"
             "                   sb_jnl_blocks: (U32) {0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X, 0x%08X,\n"
             "                                         0x%08X},\n"
             "                   sb_inode_size_extra_min: (U16) %u,\n"
             "                   sb_inode_size_extra_want: (U16) %u,\n"
             "                   sb_flags: ",
             le32toh(sb->sb_first_meta_block_group),
             newfs_time, str_newfs_time,
             le32toh(sb->sb_jnl_blocks[0]), le32toh(sb->sb_jnl_blocks[1]),
             le32toh(sb->sb_jnl_blocks[2]), le32toh(sb->sb_jnl_blocks[3]),
             le32toh(sb->sb_jnl_blocks[4]), le32toh(sb->sb_jnl_blocks[5]),
             le32toh(sb->sb_jnl_blocks[6]), le32toh(sb->sb_jnl_blocks[7]),
             le32toh(sb->sb_jnl_blocks[8]), le32toh(sb->sb_jnl_blocks[9]),
             le32toh(sb->sb_jnl_blocks[10]), le32toh(sb->sb_jnl_blocks[11]),
             le32toh(sb->sb_jnl_blocks[12]), le32toh(sb->sb_jnl_blocks[13]),
             le32toh(sb->sb_jnl_blocks[14]), le32toh(sb->sb_jnl_blocks[15]),
             le32toh(sb->sb_jnl_blocks[16]),
             le16toh(sb->sb_inode_size_extra_min),
             le16toh(sb->sb_inode_size_extra_want));
      ext4fs_inspect_flag_names(le32toh(sb->sb_flags), ext4fs_flag_names);
      printf(",\n"
             "                   sb_raid_stride_block_count: %u,\n"
             "                   sb_mmp_interval: (U32) %u,\n"
             "                   sb_mmp_block: (U32) %u,\n"
             "                   sb_raid_stripe_width_block_count: (U32) %u,\n"
             "                   sb_log_groups_per_flex: %u,\t\t# %u,\n"
             "                   sb_checksum_type: ",
             le16toh(sb->sb_raid_stride_block_count),
             le32toh(sb->sb_mmp_interval),
             le32toh(sb->sb_mmp_block),
             le32toh(sb->sb_raid_stripe_width_block_count),
             sb->sb_log_groups_per_flex, 1 << sb->sb_log_groups_per_flex);
      ext4fs_inspect_flag_names(le16toh(sb->sb_checksum_type),
                                ext4fs_checksum_type_names);
      printf(",\n"
             "                   sb_kilobytes_written: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                   sb_error_count: (U32) %u,\n"
             "                   sb_first_error_time: (U64) "
             CONFIGURE_FMT_UINT64 ",\t# %s\n"
             "                   sb_first_error_inode: (U32) %u,\n"
             "                   sb_first_error_block: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                   sb_first_error_function: \"%s\",\n"
             "                   sb_first_error_line: (U32) %u,\n"
             "                   sb_last_error_time: (U64) "
             CONFIGURE_FMT_UINT64 ",\t# %s\n"
             "                   sb_last_error_inode: (U32) %u,\n"
             "                   sb_last_error_line: (U32) %u,\n"
             "                   sb_last_error_block: (U64) "
             CONFIGURE_FMT_UINT64 ",\n"
             "                   sb_last_error_function: \"%s\",\n"
             "                   sb_mount_opts: \"%s\",\n"
             "                   sb_user_quota_inode: (U32) %u,\n"
             "                   sb_group_quota_inode: (U32) %u,\n"
             "                   sb_overhead_clusters: (U32) %u,\n"
             "                   sb_backup_block_groups: (U32) {%u, %u},\n"
             "                   sb_encrypt_algos: (U8) {0x%02X, 0x%02X, 0x%02X, 0x%02X},\n"
             "                   sb_encrypt_pw_salt: (U8) {0x%02X, 0x%02X, 0x%02X, 0x%02X,\n"
             "                                             0x%02X, 0x%02X, 0x%02X, 0x%02X,\n"
             "                                             0x%02X, 0x%02X, 0x%02X, 0x%02X,\n"
             "                                             0x%02X, 0x%02X, 0x%02X, 0x%02X},\n"
             "                   sb_lost_and_found_inode: (U32) %u,\n"
             "                   sb_project_quota_inode: (U32) %u,\n"
             "                   sb_checksum_seed: (U32) %u,\n"
             "                   sb_first_error_code: %u,\n"
             "                   sb_last_error_code: %u,\n"
             "                   sb_encoding: ",
             le64toh(sb->sb_kilobytes_written),
             le32toh(sb->sb_error_count),
             first_error_time, str_first_error_time,
             le32toh(sb->sb_first_error_inode),
             le64toh(sb->sb_first_error_block),
             first_error_function,
             le32toh(sb->sb_first_error_line),
             last_error_time, str_last_error_time,
             le32toh(sb->sb_last_error_inode),
             le32toh(sb->sb_last_error_line),
             le64toh(sb->sb_last_error_block),
             last_error_function,
             mount_opts,
             le32toh(sb->sb_user_quota_inode),
             le32toh(sb->sb_group_quota_inode),
             le32toh(sb->sb_overhead_clusters),
             le32toh(sb->sb_backup_block_groups[0]),
             le32toh(sb->sb_backup_block_groups[1]),
             sb->sb_encrypt_algos[0], sb->sb_encrypt_algos[1],
             sb->sb_encrypt_algos[2], sb->sb_encrypt_algos[3],
             sb->sb_encrypt_pw_salt[0], sb->sb_encrypt_pw_salt[1],
             sb->sb_encrypt_pw_salt[2], sb->sb_encrypt_pw_salt[3],
             sb->sb_encrypt_pw_salt[4], sb->sb_encrypt_pw_salt[5],
             sb->sb_encrypt_pw_salt[6], sb->sb_encrypt_pw_salt[7],
             sb->sb_encrypt_pw_salt[8], sb->sb_encrypt_pw_salt[9],
             sb->sb_encrypt_pw_salt[10], sb->sb_encrypt_pw_salt[11],
             sb->sb_encrypt_pw_salt[12], sb->sb_encrypt_pw_salt[13],
             sb->sb_encrypt_pw_salt[14], sb->sb_encrypt_pw_salt[15],
             le32toh(sb->sb_lost_and_found_inode),
             le32toh(sb->sb_project_quota_inode),
             le32toh(sb->sb_checksum_seed),
             sb->sb_first_error_code,
             sb->sb_last_error_code);
      ext4fs_inspect_flag_names(le16toh(sb->sb_encoding),
                                ext4fs_encoding_names);
      printf(",\n"
             "                   sb_encoding_flags: ");
      ext4fs_inspect_flag_names(le16toh(sb->sb_encoding_flags),
                                ext4fs_encoding_flag_names);
      printf(",\n"
             "                   sb_orphan_file_inode: (U32) %u,\n"
             "                   sb_checksum: (U32) 0x%08X} # 0x%08X\n",
             le32toh(sb->sb_orphan_file_inode),
             checksum, checksum_computed);
      return 0;
    }
    
    uint64_t ext4fs_sb_block_group_count (const struct ext4fs_super_block *sb)
    {
      uint32_t blocks_per_group;
      uint64_t count;
      assert(sb);
      count = le32toh(sb->sb_blocks_count_lo);
      if (ext4fs_64bit(sb))
        count |= (uint64_t) le32toh(sb->sb_blocks_count_hi) << 32;
      blocks_per_group = le32toh(sb->sb_blocks_per_group);
      count += blocks_per_group - 1;
      count /= blocks_per_group;
      return count;
    }
    
    int ext4fs_sb_block_size (const struct ext4fs_super_block *sb,
                              uint32_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = 1 << (sb->sb_log_block_size + 10);
      return 0;
    }
    
    int ext4fs_sb_blocks_count (const struct ext4fs_super_block *sb,
                                uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_blocks_count_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(sb->sb_blocks_count_hi) << 32;
      return 0;
    }
    
    int ext4fs_sb_check_time (const struct ext4fs_super_block *sb,
                              uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_check_time_lo);
      if (ext4fs_64bit(sb))
        *dest |= ((uint64_t) sb->sb_check_time_hi) << 32;
      return 0;
    }
    
    int
    ext4fs_sb_checksum_compute
    (const struct ext4fs_super_block *sb,
     uint32_t *dest)
    {
      uint32_t crc = 0;
      assert(sb);
      assert(dest);
      if (! (sb->sb_feature_ro_compat & EXT4FS_FEATURE_RO_COMPAT_METADATA_CSUM)) {
        *dest = 0;
        return 0;
      }
      crc = crc32c(crc, sb, offsetof(struct ext4fs_super_block, sb_checksum));
      *dest = ~crc;
      return 0;
    }
    
    int ext4fs_sb_first_error_time (const struct ext4fs_super_block *sb,
                                 uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_first_error_time_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) sb->sb_first_error_time_hi << 32;
      return 0;
    }
    
    int ext4fs_sb_free_blocks_count (const struct ext4fs_super_block *sb,
                                  uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_free_blocks_count_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(sb->sb_free_blocks_count_hi) << 32;
      return 0;
    }
    
    int ext4fs_sb_inode_size (const struct ext4fs_super_block *sb,
                              uint16_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le16toh(sb->sb_inode_size);
      return 0;
    }
    
    int ext4fs_sb_last_error_time (const struct ext4fs_super_block *sb,
                                   uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_last_error_time_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) sb->sb_last_error_time_hi << 32;
      return 0;
    }
    
    int ext4fs_sb_mount_time (const struct ext4fs_super_block *sb,
                              uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_mount_time_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) sb->sb_mount_time_hi << 32;
      return 0;
    }
    
    int ext4fs_sb_newfs_time (const struct ext4fs_super_block *sb,
                              uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_newfs_time_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) sb->sb_newfs_time_hi << 32;
      return 0;
    }
    
    int ext4fs_sb_reserved_blocks_count (const struct ext4fs_super_block *sb,
                                         uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_reserved_blocks_count_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) le32toh(sb->sb_reserved_blocks_count_hi) << 32;
      return 0;
    }
    
    int ext4fs_size (const char *dev, int fd, uint64_t *dest)
    {
      assert(dev);
      assert(dest);
      off_t offset;
      struct stat sb = {0};
      if (fstat(fd, &sb)) {
        warn("%s: fstat", dev);
        return -1;
      }
      if (S_ISREG(sb.st_mode)) {
        if ((offset = lseek(fd, 0, SEEK_END)) == -1) {
          warn("%s: lseek SEEK_END", dev);
          return -1;
        }
        *dest = offset;
        return 0;
      }
    #if defined(OpenBSD)
      if (S_ISBLK(sb.st_mode)) {
        const char *dev_last;
        struct disklabel dl;
        struct partition *part;
        int32_t sector_size;
        if (! dev || ! dev[0]) {
          fprintf(stderr, "ext4fs_size: invalid dev\n");
          return -1;
        }
        if (! ext4fs_disklabel_get(&dl, fd))
          return -1;
        dev_last = dev + strlen(dev) - 1;
        if ('0' <= *dev_last && *dev_last <= '9')
          part = &dl.d_partitions[0];
        else if (*dev_last < 'a' || *dev_last > 'p') {
          fprintf(stderr, "ext4fs_size: %s: invalid partition letter", dev);
          return -1;
        }
        else
          part = &dl.d_partitions[*dev_last - 'a'];
        if (DL_GETPSIZE(part) == 0)
          fprintf(stderr, "ext4fs_size: %s: partition is unavailable", dev);
        sector_size = dl.d_secsize;
        if (sector_size <= 0) {
          fprintf(stderr, "ext4fs_size: %s: no sector size in disklabel", dev);
          return -1;
        }
        *dest = DL_GETPSIZE(part) / sector_size * sector_size;
        return 0;
      }
    #else
      if (S_ISBLK(sb.st_mode)) {
        if (ioctl(fd, BLKGETSIZE64, dest) < 0) {
          warn("%s: ioctl BLKGETSIZE64", dev);
          return -1;
        }
        return 0;
      }
    #endif
      return 0;
    }
    
    struct ext4fs_super_block *
    ext4fs_super_block_read (struct ext4fs_super_block *sb,
                             int fd)
    {
      ssize_t done;
      ssize_t r;
      ssize_t remaining;
      assert(sb);
      if (lseek(fd, EXT4FS_SUPER_BLOCK_OFFSET, SEEK_SET) < 0) {
        warn("ext4fs_super_block_read: lseek 1024");
        return NULL;
      }
      done = 0;
      remaining = sizeof(struct ext4fs_super_block);
      while (remaining > 0) {
        r = read(fd, (char *) sb + done, remaining);
        if (r < 0) {
          warn("ext4fs_super_block_read: read super block %ld", remaining);
          return NULL;
        }
        done += r;
        remaining -= r;
      }
      return sb;
    }
    
    int ext4fs_time_to_str (time_t time, char *str, size_t size)
    {
      struct tm *local;
      assert(str);
      if (size < 25) {
        fprintf(stderr, "time_to_str: size < 25\n");
        return -1;
      }
      local = localtime(&time);
      if (! strftime(str, size, "%F %T %Z", local)) {
        fprintf(stderr, "time_to_str: strftime\n");
        return -1;
      }
      return 0;
    }
    
    int ext4fs_sb_write_time (const struct ext4fs_super_block *sb,
                              uint64_t *dest)
    {
      assert(sb);
      assert(dest);
      *dest = le32toh(sb->sb_write_time_lo);
      if (ext4fs_64bit(sb))
        *dest |= (uint64_t) sb->sb_write_time_hi << 32;
      return 0;
    }