Branch :
/* 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.
*/
#define _DEFAULT_SOURCE 1
#include <endian.h>
#include <err.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <linux/fs.h>
#include <ext4fs.h>
const s_enum ext4fs_feature_compat[] = {
{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_enum ext4fs_feature_incompat[] = {
{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_enum ext4fs_feature_ro_compat[] = {
{EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER, "sparse_super"},
{EXT4_FEATURE_RO_COMPAT_LARGE_FILE, "large_file"},
{EXT4_FEATURE_RO_COMPAT_BTREE_DIR, "btree_dir"},
{EXT4_FEATURE_RO_COMPAT_HUGE_FILE, "huge_file"},
{EXT4_FEATURE_RO_COMPAT_GDT_CSUM, "gdt_csum"},
{EXT4_FEATURE_RO_COMPAT_DIR_NLINK, "dir_nlink"},
{EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE, "extra_isize"},
{EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT, "has_snapshot"},
{EXT4_FEATURE_RO_COMPAT_QUOTA, "quota"},
{EXT4_FEATURE_RO_COMPAT_BIGALLOC, "bigalloc"},
{EXT4_FEATURE_RO_COMPAT_METADATA_CSUM, "metadata_csum"},
{EXT4_FEATURE_RO_COMPAT_REPLICA, "replica"},
{EXT4_FEATURE_RO_COMPAT_READONLY, "readonly"},
{EXT4_FEATURE_RO_COMPAT_PROJECT, "project"},
{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_block_bitmap (const struct ext4fs_super_block *sb,
const struct ext4fs_group_desc *gd,
uint64_t *dest)
{
if (! gd) {
warnx("ext4fs_block_bitmap: NULL group descriptor");
return -1;
}
if (! dest) {
warnx("ext4fs_block_bitmap: NULL dest");
return -1;
}
*dest = le32toh(gd->gd_block_bitmap_lo);
if (ext4fs_64bit(sb))
*dest |= ((uint64_t) le32toh(gd->gd_block_bitmap_hi) << 32);
return 0;
}
int ext4fs_block_size (const struct ext4fs_super_block *sb,
uint32_t *dest)
{
if (! sb) {
warnx("ext4fs_block_size: NULL super block");
return -1;
}
if (! dest) {
warnx("ext4fs_block_size: NULL dest");
return -1;
}
*dest = 1 << (sb->sb_log_block_size + 10);
return 0;
}
int ext4fs_blocks_count (const struct ext4fs_super_block *sb,
uint64_t *dest)
{
if (! sb) {
warnx("ext4fs_blocks_count: NULL super block");
return -1;
}
if (! dest) {
warnx("ext4fs_blocks_count: NULL dest");
return -1;
}
*dest = le32toh(sb->sb_blocks_count_lo);
if (ext4fs_64bit(sb))
*dest |= ((uint64_t) le32toh(sb->sb_blocks_count_hi) << 32);
return 0;
}
struct ext4fs_group_desc *
ext4fs_group_desc_read (struct ext4fs_group_desc *gd,
int fd,
const struct ext4fs_super_block *sb)
{
uint32_t block_size;
ssize_t done;
uint32_t offset;
ssize_t r;
ssize_t remaining;
if (ext4fs_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_group_desc);
while (remaining > 0) {
r = read(fd, (char *) gd + done, remaining);
if (r < 0) {
warn("read super block %ld", remaining);
return NULL;
}
done += r;
remaining -= r;
}
return gd;
}
int ext4fs_inode_bitmap (const struct ext4fs_super_block *sb,
const struct ext4fs_group_desc *gd,
uint64_t *dest)
{
if (! gd) {
warnx("ext4fs_inode_bitmap: NULL group descriptor");
return -1;
}
if (! dest) {
warnx("ext4fs_inode_bitmap: NULL dest");
return -1;
}
*dest = le32toh(gd->gd_inode_bitmap_lo);
if (ext4fs_64bit(sb))
*dest |= ((uint64_t) le32toh(gd->gd_inode_bitmap_hi) << 32);
return 0;
}
int ext4fs_inode_table (const struct ext4fs_super_block *sb,
const struct ext4fs_group_desc *gd,
uint64_t *dest)
{
if (! gd) {
warnx("ext4fs_inode_table: NULL group descriptor");
return -1;
}
if (! dest) {
warnx("ext4fs_inode_table: NULL dest");
return -1;
}
*dest = le32toh(gd->gd_inode_table_lo);
if (ext4fs_64bit(sb))
*dest |= ((uint64_t) le32toh(gd->gd_inode_table_hi) << 32);
return 0;
}
int ext4fs_inspect (int fd)
{
struct ext4fs_group_desc gd = {0};
struct ext4fs_super_block sb = {0};
uint64_t size = 0;
if (ext4fs_size(fd, &size) ||
! size)
return -1;
printf("ext4fs_size: %lu\n", size);
if (! ext4fs_super_block_read(&sb, fd))
return -1;
if (ext4fs_inspect_super_block(&sb))
return -1;
if (! ext4fs_group_desc_read(&gd, fd, &sb))
return -1;
if (ext4fs_inspect_group_desc(&sb, &gd))
return -1;
printf("EOF\n");
return 0;
}
int ext4fs_inspect_group_desc (const struct ext4fs_super_block *sb,
const struct ext4fs_group_desc *gd)
{
uint64_t block_bitmap;
uint64_t inode_bitmap;
uint64_t inode_table;
if (ext4fs_block_bitmap(sb, gd, &block_bitmap) ||
ext4fs_inode_bitmap(sb, gd, &inode_bitmap) ||
ext4fs_inode_table(sb, gd, &inode_table))
return -1;
printf("%%Ext4fs.GroupDesc{gd_block_bitmap: %lu,\n"
" gd_inode_bitmap: %lu,\n"
" gd_inode_table: %lu}\n",
block_bitmap,
inode_bitmap,
inode_table);
return 0;
}
int ext4fs_inspect_super_block (const struct ext4fs_super_block *sb)
{
uint64_t blocks_count;
const s_enum *e;
uint32_t feature;
int first;
if (ext4fs_blocks_count(sb, &blocks_count))
return -1;
printf("%%Ext4fs.SuperBlock{sb_inodes_count: %u,\n"
" sb_blocks_count: %lu,\n"
" sb_rev_level: %u,\n"
" sb_rev_level_minor: %u,\n"
" sb_feature_compat: ",
le32toh(sb->sb_inodes_count),
blocks_count,
le32toh(sb->sb_rev_level),
le32toh(sb->sb_rev_level_minor));
feature = le32toh(sb->sb_feature_compat);
e = ext4fs_feature_compat;
first = 1;
while (e->name) {
if (feature & e->value) {
if (! first)
printf("|");
else
first = 0;
printf("%s", e->name);
}
e++;
}
printf(",\n"
" sb_feature_incompat: ");
feature = le32toh(sb->sb_feature_incompat);
e = ext4fs_feature_incompat;
first = 1;
while (e->name) {
if (feature & e->value) {
if (! first)
printf("|");
else
first = 0;
printf("%s", e->name);
}
e++;
}
printf(",\n"
" sb_feature_ro_compat: ");
feature = le32toh(sb->sb_feature_ro_compat);
e = ext4fs_feature_ro_compat;
first = 1;
while (e->name) {
if (feature & e->value) {
if (! first)
printf("|");
else
first = 0;
printf("%s", e->name);
}
e++;
}
printf("}\n");
return 0;
}
int ext4fs_size (int fd, uint64_t *dest)
{
if (ioctl(fd, BLKGETSIZE64, dest) < 0) {
warn("ioctl BLKGETSIZE64");
return -1;
}
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;
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;
}