From dcd9833762ba7d29bfa31beefa6f0e98fda32f16 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 17 Apr 2026 20:37:19 -0600 Subject: [PATCH 01/12] Test wheels --- .github/workflows/build.yaml | 27 +++++++++++++----- pyproject.toml | 4 +-- test-wheel.sh | 53 ++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 test-wheel.sh diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5bd16d9..2434fd5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,7 +6,7 @@ on: pull_request: workflow_dispatch: release: - types: [released] + types: [ released ] permissions: read-all jobs: lint: @@ -61,7 +61,7 @@ jobs: test: name: Test on ${{ matrix.os }} python ${{ matrix.python }} runs-on: ${{ matrix.os }} - needs: [test-image] + needs: [ test-image ] strategy: fail-fast: false matrix: @@ -112,7 +112,7 @@ jobs: build-sdist: name: Build pip package runs-on: ubuntu-latest - needs: [test, lint] + needs: [ test, lint ] steps: - name: Checkout the Git repository uses: actions/checkout@v6 @@ -133,7 +133,7 @@ jobs: build-any-wheel: name: Build wheel runs-on: ubuntu-latest - needs: [test, lint] + needs: [ test, lint ] steps: - name: Checkout the Git repository uses: actions/checkout@v6 @@ -154,7 +154,7 @@ jobs: build-wheel: name: Build wheel if: github.repository == 'Eeems/python-ext4' && github.event_name == 'release' && startsWith(github.ref, 'refs/tags') - needs: [lint, test] + needs: [ lint, test ] runs-on: ubuntu-latest strategy: fail-fast: false @@ -166,18 +166,31 @@ jobs: - ppc64le - aarch64 - armv7l + - riscv64 + - s390x libc: - glibc - # - musl + - musl steps: - name: Checkout the Git repository uses: actions/checkout@v6 - - name: Building package + - name: Building wheel run: ./wheel.sh env: python: ${{ matrix.python }} arch: ${{ matrix.arch }} libc: ${{ matrix.libc }} + - name: Download test.ext4 + uses: actions/download-artifact@v8 + with: + name: test.ext4 + path: . + - name: Testing wheel + run: ./test-wheel.sh + env: + python: ${{ matrix.python }} + arch: ${{ matrix.arch }} + libc: ${{ matrix.libc }} - uses: actions/upload-artifact@v6 with: name: pip-wheel-${{ matrix.python }}-${{ matrix.arch }}-${{ matrix.libc }} diff --git a/pyproject.toml b/pyproject.toml index aa19573..18b1f86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,7 @@ dev = [ 'ruff', 'basedpyright', ] -test = [ - "pytest", -] +test = [] fuzz = [ "atheris", ] diff --git a/test-wheel.sh b/test-wheel.sh new file mode 100644 index 0000000..a3367e3 --- /dev/null +++ b/test-wheel.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -e +libc=${libc:-glibc} +arch=${arch:-x86_64} +python=${python:-3.11} + +wheel="$(find wheelhouse -name "*_${arch}.whl" | head -n1)" +script=$( + cat < Date: Fri, 17 Apr 2026 20:37:37 -0600 Subject: [PATCH 02/12] Version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 18b1f86..195fe9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ext4" -version = "1.3.1" +version = "1.3.2" authors = [ { name="Eeems", email="eeems@eeems.email" }, ] From 22a6bbd42fa4f4fbce107bb8da5f695c7b7cbf6a Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 17 Apr 2026 20:42:19 -0600 Subject: [PATCH 03/12] Always build wheels --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2434fd5..12b264a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -153,7 +153,7 @@ jobs: if-no-files-found: error build-wheel: name: Build wheel - if: github.repository == 'Eeems/python-ext4' && github.event_name == 'release' && startsWith(github.ref, 'refs/tags') + # if: github.repository == 'Eeems/python-ext4' && github.event_name == 'release' && startsWith(github.ref, 'refs/tags') needs: [ lint, test ] runs-on: ubuntu-latest strategy: From 414a15e8fa1385f07c02d6407af9ebafe7c1c572 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 17 Apr 2026 20:53:21 -0600 Subject: [PATCH 04/12] Guard against missing wheel --- test-wheel.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test-wheel.sh b/test-wheel.sh index a3367e3..ce12379 100644 --- a/test-wheel.sh +++ b/test-wheel.sh @@ -5,6 +5,10 @@ arch=${arch:-x86_64} python=${python:-3.11} wheel="$(find wheelhouse -name "*_${arch}.whl" | head -n1)" +if [[ -z "$wheel" ]]; then + echo "No wheel found for architecture $arch" + exit 1 +fi script=$( cat < Date: Fri, 17 Apr 2026 20:54:53 -0600 Subject: [PATCH 05/12] Make wheel test script executable --- test-wheel.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 test-wheel.sh diff --git a/test-wheel.sh b/test-wheel.sh old mode 100644 new mode 100755 From 5503a8b26e94cc26a3a0a3c4b59c2bd3b5046a5c Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 17 Apr 2026 22:20:57 -0600 Subject: [PATCH 06/12] Fix endianness on s390x --- ext4/inode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext4/inode.py b/ext4/inode.py index ad6a15d..811828d 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -6,8 +6,8 @@ import warnings from collections.abc import Generator from ctypes import ( + BigEndianUnion, LittleEndianStructure, - Union, c_uint16, c_uint32, sizeof, @@ -103,7 +103,7 @@ class Masix1(LittleEndianStructure): @final -class Osd1(Union): +class Osd1(BigEndianUnion): _pack_ = 1 _fields_ = [ ("linux1", Linux1), @@ -151,7 +151,7 @@ class Masix2(LittleEndianStructure): @final -class Osd2(Union): +class Osd2(BigEndianUnion): _pack_ = 1 _fields_ = [ ("linux2", Linux2), From 72241311c8bdff08347c749192e04f35633dff48 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 17 Apr 2026 22:22:51 -0600 Subject: [PATCH 07/12] Drop 3.10 support --- .github/workflows/build.yaml | 2 -- pyproject.toml | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 12b264a..3e9f849 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,7 +16,6 @@ jobs: fail-fast: false matrix: python: &python-versions - - "3.10" - "3.11" - "3.12" - "3.13" @@ -93,7 +92,6 @@ jobs: fail-fast: false matrix: python: - #- "3.10" # atheris appears to break - "3.11" - "3.12" - "3.13" diff --git a/pyproject.toml b/pyproject.toml index 195fe9d..83b3a60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ authors = [ { name="Eeems", email="eeems@eeems.email" }, ] description = "Library for read only interactions with an ext4 filesystem" -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", @@ -15,7 +15,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", From 6e1871c89d6ff1838d272b02d0b24eafc3d3b895 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 17 Apr 2026 22:23:55 -0600 Subject: [PATCH 08/12] Fix riscv64 build --- wheel.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wheel.sh b/wheel.sh index 91f67ff..85e6db2 100755 --- a/wheel.sh +++ b/wheel.sh @@ -23,6 +23,8 @@ if [[ "$libc" == "musl" ]]; then image="musllinux_1_2_$arch" elif [[ "$arch" == "armv7l" ]]; then image="manylinux_2_35_$arch" +elif [[ "$arch" == "riscv64" ]]; then + image="manylinux_2_39_$arch" else image="manylinux_2_34_$arch" fi From da9d2ba8e342947cde5be636b2270a63eb181997 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 18 Apr 2026 01:45:46 -0600 Subject: [PATCH 09/12] Fix endianness issues with s390x --- ext4/blockdescriptor.py | 2 +- ext4/directory.py | 4 ++-- ext4/enum.py | 1 + ext4/htree.py | 2 +- ext4/inode.py | 22 ++++++++++++---------- ext4/superblock.py | 38 ++++++++++++++++---------------------- test.py | 11 +++++++++-- 7 files changed, 42 insertions(+), 38 deletions(-) diff --git a/ext4/blockdescriptor.py b/ext4/blockdescriptor.py index 2e686a9..705f979 100644 --- a/ext4/blockdescriptor.py +++ b/ext4/blockdescriptor.py @@ -30,7 +30,7 @@ class BlockDescriptor(Ext4Struct): ("bg_free_blocks_count_lo", c_uint16), ("bg_free_inodes_count_lo", c_uint16), ("bg_used_dirs_count_lo", c_uint16), - ("bg_flags", EXT4_BG), + ("bg_flags", EXT4_BG.basetype), ("bg_exclude_bitmap_lo", c_uint32), ("bg_block_bitmap_csum_lo", c_uint16), ("bg_inode_bitmap_csum_lo", c_uint16), diff --git a/ext4/directory.py b/ext4/directory.py index c6f1508..f9f5511 100644 --- a/ext4/directory.py +++ b/ext4/directory.py @@ -73,13 +73,13 @@ class DirectoryEntry2(DirectoryEntryBase): ("inode", c_uint32), ("rec_len", c_uint16), ("name_len", c_uint8), - ("file_type", EXT4_FT), + ("file_type", EXT4_FT.basetype), ("name", c_char * EXT4_NAME_LEN), ] @DirectoryEntryBase.is_fake_entry.getter def is_fake_entry(self) -> bool: - file_type = assert_cast(self.file_type, EXT4_FT) # pyright: ignore[reportAny] + file_type = EXT4_FT(self.file_type) # pyright: ignore[reportAny] return super().is_fake_entry or file_type == EXT4_FT.DIR_CSUM diff --git a/ext4/enum.py b/ext4/enum.py index 3648f87..3378dd1 100644 --- a/ext4/enum.py +++ b/ext4/enum.py @@ -48,6 +48,7 @@ def __repr__(self) -> str: def TypedCEnumeration(_type: type["SimpleCData"]): # noqa: ANN201 class CEnumeration(_type, metaclass=TypedEnumerationType(_type)): # pyright: ignore[reportGeneralTypeIssues, reportUntypedBaseClass] # noqa: ANN201,PLW1641,PLW1641 _members_: dict[str, Any] = {} # pyright: ignore[reportExplicitAny] + basetype: type["SimpleCData"] = _type @override def __repr__(self) -> str: diff --git a/ext4/htree.py b/ext4/htree.py index afb8e60..b39d925 100644 --- a/ext4/htree.py +++ b/ext4/htree.py @@ -80,7 +80,7 @@ class DXRootInfo(LittleEndianStructure): # _anonymous_ = ("reserved_zero") _fields_ = [ ("reserved_zero", c_uint32), - ("hash_version", DX_HASH), + ("hash_version", DX_HASH.basetype), ("info_length", c_uint8), ("indirect_levels", c_uint8), ("unused_flags", c_uint8), diff --git a/ext4/inode.py b/ext4/inode.py index 811828d..114ca14 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -6,8 +6,8 @@ import warnings from collections.abc import Generator from ctypes import ( - BigEndianUnion, LittleEndianStructure, + LittleEndianUnion, c_uint16, c_uint32, sizeof, @@ -103,7 +103,7 @@ class Masix1(LittleEndianStructure): @final -class Osd1(BigEndianUnion): +class Osd1(LittleEndianUnion): _pack_ = 1 _fields_ = [ ("linux1", Linux1), @@ -151,7 +151,7 @@ class Masix2(LittleEndianStructure): @final -class Osd2(BigEndianUnion): +class Osd2(LittleEndianUnion): _pack_ = 1 _fields_ = [ ("linux2", Linux2), @@ -165,7 +165,7 @@ class Inode(Ext4Struct): EXT2_GOOD_OLD_INODE_SIZE: int = 128 _pack_ = 1 # pyright: ignore[reportUnannotatedClassAttribute] _fields_ = [ # pyright: ignore[reportUnannotatedClassAttribute] - ("i_mode", MODE), + ("i_mode", MODE.basetype), ("i_uid", c_uint16), ("i_size_lo", c_uint32), ("i_atime", c_uint32), @@ -175,7 +175,7 @@ class Inode(Ext4Struct): ("i_gid", c_uint16), ("i_links_count", c_uint16), ("i_blocks_lo", c_uint32), - ("i_flags", EXT4_FL), + ("i_flags", EXT4_FL.basetype), ("osd1", Osd1), ("i_block", c_uint32 * 15), ("i_generation", c_uint32), @@ -199,9 +199,11 @@ def get_file_type(cls, volume: Volume, offset: int) -> EXT4_FT: _ = volume.seek(offset + Inode.i_mode.offset) file_type = cast( MODE, - Inode.field_type("i_mode").from_buffer_copy( # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType, reportOptionalMemberAccess] + Inode.field_type("i_mode") + .from_buffer_copy( # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType, reportOptionalMemberAccess] volume.read(Inode.i_mode.size) ) + .value & 0xF000, ) match file_type: @@ -321,7 +323,7 @@ def seed(self) -> int: @Ext4Struct.checksum.getter def checksum(self) -> int | None: - s_creator_os: EXT4_OS = assert_cast(self.superblock.s_creator_os, EXT4_OS) # pyright: ignore[reportAny] + s_creator_os: EXT4_OS = EXT4_OS(self.superblock.s_creator_os) # pyright: ignore[reportAny] if s_creator_os != EXT4_OS.LINUX: return None @@ -358,7 +360,7 @@ def checksum(self) -> int | None: @Ext4Struct.expected_checksum.getter def expected_checksum(self) -> int | None: - s_creator_os = assert_cast(self.superblock.s_creator_os, EXT4_OS) # pyright: ignore[reportAny] + s_creator_os = EXT4_OS(self.superblock.s_creator_os) # pyright: ignore[reportAny] if s_creator_os != EXT4_OS.LINUX: return None @@ -378,7 +380,7 @@ def validate(self) -> None: self.tree.validate() def has_flag(self, flag: EXT4_FL | int) -> bool: - i_flags = assert_cast(self.i_flags, EXT4_FL) # pyright: ignore[reportAny] + i_flags = EXT4_FL(self.i_flags) # pyright: ignore[reportAny] return (i_flags & flag) != 0 @property @@ -610,7 +612,7 @@ def opendir( ) -> Generator[tuple[DirectoryEntry | DirectoryEntry2, EXT4_FT], Any, None]: # pyright: ignore[reportExplicitAny] for dirent in self._opendir(): if isinstance(dirent, DirectoryEntry2): - file_type = assert_cast(dirent.file_type, EXT4_FT) # pyright: ignore[reportAny] + file_type = EXT4_FT(dirent.file_type) # pyright: ignore[reportAny] if file_type == EXT4_FT.DIR_CSUM: continue diff --git a/ext4/superblock.py b/ext4/superblock.py index bae84c2..770871e 100644 --- a/ext4/superblock.py +++ b/ext4/superblock.py @@ -59,21 +59,21 @@ class Superblock(Ext4Struct): ("s_mnt_count", c_uint16), ("s_max_mnt_count", c_uint16), ("s_magic", c_uint16), # 0xEF53 - ("s_state", EXT4_FS), - ("s_errors", EXT4_ERRORS), + ("s_state", EXT4_FS.basetype), + ("s_errors", EXT4_ERRORS.basetype), ("s_minor_rev_level", c_uint16), ("s_lastcheck", c_uint32), ("s_checkinterval", c_uint32), - ("s_creator_os", EXT4_OS), - ("s_rev_level", EXT4_REV), + ("s_creator_os", EXT4_OS.basetype), + ("s_rev_level", EXT4_REV.basetype), ("s_def_resuid", c_uint16), ("s_def_resgid", c_uint16), ("s_first_ino", c_uint32), ("s_inode_size", c_uint16), ("s_block_group_nr", c_uint16), - ("s_feature_compat", EXT4_FEATURE_COMPAT), - ("s_feature_incompat", EXT4_FEATURE_INCOMPAT), - ("s_feature_ro_compat", EXT4_FEATURE_RO_COMPAT), + ("s_feature_compat", EXT4_FEATURE_COMPAT.basetype), + ("s_feature_incompat", EXT4_FEATURE_INCOMPAT.basetype), + ("s_feature_ro_compat", EXT4_FEATURE_RO_COMPAT.basetype), ("s_uuid", c_uint8 * 16), ("s_volume_name", c_ubyte * 16), ("s_last_mounted", c_ubyte * 64), @@ -86,10 +86,10 @@ class Superblock(Ext4Struct): ("s_journal_dev", c_uint32), ("s_last_orphan", c_uint32), ("s_hash_seed", c_uint32 * 4), - ("s_def_hash_version", DX_HASH), + ("s_def_hash_version", DX_HASH.basetype), ("s_jnl_backup_type", c_uint8), ("s_desc_size", c_uint16), - ("s_default_mount_opts", EXT4_DEFM), + ("s_default_mount_opts", EXT4_DEFM.basetype), ("s_first_meta_bg", c_uint32), ("s_mkfs_time", c_uint32), ("s_jnl_blocks", c_uint32 * 17), @@ -98,13 +98,13 @@ class Superblock(Ext4Struct): ("s_free_blocks_count_hi", c_uint32), ("s_min_extra_isize", c_uint16), ("s_want_extra_isize", c_uint16), - ("s_flags", EXT2_FLAGS), + ("s_flags", EXT2_FLAGS.basetype), ("s_raid_stride", c_uint16), ("s_mmp_interval", c_uint16), ("s_mmp_block", c_uint64), ("s_raid_stripe_width", c_uint32), ("s_log_groups_per_flex", c_uint8), - ("s_checksum_type", EXT4_CHKSUM), + ("s_checksum_type", EXT4_CHKSUM.basetype), ("s_reserved_pad", c_uint16), ("s_kbytes_written", c_uint64), ("s_snapshot_inum", c_uint32), @@ -122,12 +122,12 @@ class Superblock(Ext4Struct): ("s_last_error_line", c_uint32), ("s_last_error_block", c_uint64), ("s_last_error_func", c_uint8 * 32), - ("s_mount_opts", EXT4_MOUNT * 64), # pyright: ignore[reportOperatorIssue] + ("s_mount_opts", EXT4_MOUNT.basetype * 64), # pyright: ignore[reportOperatorIssue] ("s_usr_quota_inum", c_uint32), ("s_grp_quota_inum", c_uint32), ("s_overhead_blocks", c_uint32), ("s_backup_bgs", c_uint32 * 2), - ("s_encrypt_algos", FS_ENCRYPTION_MODE * 4), # pyright: ignore[reportOperatorIssue] + ("s_encrypt_algos", FS_ENCRYPTION_MODE.basetype * 4), # pyright: ignore[reportOperatorIssue] ("s_encrypt_pw_salt", c_uint8 * 16), ("s_lpf_ino", c_uint32), ("s_prj_quota_inum", c_uint32), @@ -206,21 +206,15 @@ def checksum(self) -> int | None: @property def feature_incompat(self) -> EXT4_FEATURE_INCOMPAT: - s_feature_incompat = assert_cast(self.s_feature_incompat, EXT4_FEATURE_INCOMPAT) # pyright: ignore[reportAny] - return s_feature_incompat + return EXT4_FEATURE_INCOMPAT(self.s_feature_incompat) # pyright: ignore[reportAny] @property def feature_compat(self) -> EXT4_FEATURE_COMPAT: - s_feature_compat = assert_cast(self.s_feature_compat, EXT4_FEATURE_COMPAT) # pyright: ignore[reportAny] - return s_feature_compat + return EXT4_FEATURE_COMPAT(self.s_feature_compat) # pyright: ignore[reportAny] @property def feature_ro_compat(self) -> EXT4_FEATURE_RO_COMPAT: - s_feature_ro_compat = assert_cast( - self.s_feature_ro_compat, # pyright: ignore[reportAny] - EXT4_FEATURE_RO_COMPAT, - ) - return s_feature_ro_compat + return EXT4_FEATURE_RO_COMPAT(self.s_feature_ro_compat) # pyright: ignore[reportAny] @property def seed(self) -> int: diff --git a/test.py b/test.py index 3aac1b5..a86dd47 100644 --- a/test.py +++ b/test.py @@ -189,7 +189,10 @@ def test_root_inode(volume: ext4.Volume) -> None: htree = volume.root.htree _assert("htree is not None") if htree is not None: - _assert("isinstance(htree.dot, ext4.DotDirectoryEntry2)", lambda: htree.dot) # pyright: ignore[reportOptionalMemberAccess, reportAny] + _assert( + "isinstance(htree.dot, ext4.DotDirectoryEntry2)", + lambda: htree.dot, # pyright: ignore[reportOptionalMemberAccess, reportAny] + ) _assert( "isinstance(htree.dotdot, ext4.DotDirectoryEntry2)", lambda: htree.dotdot, # pyright: ignore[reportOptionalMemberAccess, reportAny] @@ -208,7 +211,11 @@ def test_root_inode(volume: ext4.Volume) -> None: dx_root_info = htree.dx_root_info # pyright: ignore[reportAny] _assert( - "isinstance(dx_root_info.hash_version, ext4.DX_HASH)", + "isinstance(dx_root_info.hash_version, int)", + lambda: dx_root_info.hash_version, # pyright: ignore[reportAny] + ) + _assert( + "ext4.DX_HASH(dx_root_info.hash_version)", lambda: dx_root_info.hash_version, # pyright: ignore[reportAny] ) _assert("dx_root_info.info_length == 8", lambda: dx_root_info.info_length) # pyright: ignore[reportAny] From 877a5a2425bd089cea7a8d7bf305e12bab149d7b Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 18 Apr 2026 01:50:30 -0600 Subject: [PATCH 10/12] lint fix --- ext4/superblock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext4/superblock.py b/ext4/superblock.py index 770871e..f63fc61 100644 --- a/ext4/superblock.py +++ b/ext4/superblock.py @@ -42,7 +42,7 @@ class Superblock(Ext4Struct): # "s_reserved_pad", # "s_reserved", # ) - _fields_ = [ # pyright: ignore[reportUnknownVariableType] + _fields_ = [ ("s_inodes_count", c_uint32), ("s_blocks_count_lo", c_uint32), ("s_r_blocks_count_lo", c_uint32), @@ -122,12 +122,12 @@ class Superblock(Ext4Struct): ("s_last_error_line", c_uint32), ("s_last_error_block", c_uint64), ("s_last_error_func", c_uint8 * 32), - ("s_mount_opts", EXT4_MOUNT.basetype * 64), # pyright: ignore[reportOperatorIssue] + ("s_mount_opts", EXT4_MOUNT.basetype * 64), ("s_usr_quota_inum", c_uint32), ("s_grp_quota_inum", c_uint32), ("s_overhead_blocks", c_uint32), ("s_backup_bgs", c_uint32 * 2), - ("s_encrypt_algos", FS_ENCRYPTION_MODE.basetype * 4), # pyright: ignore[reportOperatorIssue] + ("s_encrypt_algos", FS_ENCRYPTION_MODE.basetype * 4), ("s_encrypt_pw_salt", c_uint8 * 16), ("s_lpf_ino", c_uint32), ("s_prj_quota_inum", c_uint32), From eaeb827cef9edb9cd2f139204e275dc2b803e371 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 18 Apr 2026 02:05:46 -0600 Subject: [PATCH 11/12] Skip riscv64+glibc test --- test-wheel.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test-wheel.sh b/test-wheel.sh index ce12379..7bf853e 100755 --- a/test-wheel.sh +++ b/test-wheel.sh @@ -35,7 +35,11 @@ i686) echo "WARNING: Unable to test i686 as there is no suitable python image. Skipping without error for now." exit 0 ;; -ppc64le) +riscv64) + if [[ "$libc" == "glibc" ]]; then + echo "WARNING: Unable to test riscv64 as the python image doesn't support manylinux. Skipping without error for now." + exit 0 + fi platform="linux/${arch}" ;; armv7l) From 5d3625ff7623beebc4b4ec6e73d566aa583aac3f Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 18 Apr 2026 02:27:27 -0600 Subject: [PATCH 12/12] Review fixes --- .github/workflows/build.yaml | 2 +- ext4/inode.py | 11 ++--------- test.py | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3e9f849..5c14a7b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -152,7 +152,7 @@ jobs: build-wheel: name: Build wheel # if: github.repository == 'Eeems/python-ext4' && github.event_name == 'release' && startsWith(github.ref, 'refs/tags') - needs: [ lint, test ] + needs: [ lint, test, test-image ] runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/ext4/inode.py b/ext4/inode.py index 114ca14..a6a8e0d 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -15,7 +15,6 @@ from typing import ( TYPE_CHECKING, Any, - cast, final, ) @@ -197,14 +196,8 @@ class Inode(Ext4Struct): @classmethod def get_file_type(cls, volume: Volume, offset: int) -> EXT4_FT: _ = volume.seek(offset + Inode.i_mode.offset) - file_type = cast( - MODE, - Inode.field_type("i_mode") - .from_buffer_copy( # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType, reportOptionalMemberAccess] - volume.read(Inode.i_mode.size) - ) - .value - & 0xF000, + file_type = MODE( + int.from_bytes(volume.read(Inode.i_mode.size), "little") & 0xF000 ) match file_type: case MODE.IFIFO: diff --git a/test.py b/test.py index a86dd47..7c0e1cb 100644 --- a/test.py +++ b/test.py @@ -54,6 +54,20 @@ def _assert(source: str, debug: Callable[[], Any] | None = None) -> None: # pyr print(f" {debug()}", file=sys.stderr) +def _not_raises(source: str, debug: Callable[[], Any] | None = None) -> None: # pyright: ignore[reportExplicitAny] + global FAILED # noqa: PLW0603 + print(f"check {source} does not raise exception: ", end="") + try: + _ = eval(source) # noqa: S307 # pyright: ignore[reportAny] + print("pass") + + except Exception: + FAILED = True # pyright: ignore[reportConstantRedefinition] + print("fail") + if debug is not None: + print(f" {debug()}", file=sys.stderr) + + def test_magic_error(f: BufferedReader) -> None: global FAILED # noqa: PLW0603 try: @@ -214,7 +228,7 @@ def test_root_inode(volume: ext4.Volume) -> None: "isinstance(dx_root_info.hash_version, int)", lambda: dx_root_info.hash_version, # pyright: ignore[reportAny] ) - _assert( + _not_raises( "ext4.DX_HASH(dx_root_info.hash_version)", lambda: dx_root_info.hash_version, # pyright: ignore[reportAny] )