Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3723ea2
Add more type hints
Eeems Aug 16, 2025
53714ed
Force typing linting on whole codebase
Eeems Mar 10, 2026
4c5106b
Fix override import
Eeems Mar 10, 2026
cb23fac
Use assert_type
Eeems Mar 10, 2026
358c18d
Fixup assert_type
Eeems Mar 10, 2026
19e3f2f
Fix
Eeems Mar 10, 2026
ce9061f
Fix
Eeems Mar 10, 2026
8cab73e
Fix
Eeems Mar 10, 2026
3566e78
Fix 3.10
Eeems Mar 10, 2026
a7f4a12
Bump version
Eeems Mar 10, 2026
41037ab
Rename assert_type to assert_cast
Eeems Mar 10, 2026
3a596a3
Fix ge
Eeems Mar 10, 2026
918f419
Fix .. check in DotDirectoryEntry2.verify()
Eeems Mar 10, 2026
72571ac
Raise warning for DotDirectoryEntry2.verify() on ignore
Eeems Mar 10, 2026
d980574
Fix annotation
Eeems Mar 10, 2026
777a29d
Fix Ext4Struct.field_type()
Eeems Mar 10, 2026
012ee8a
Fix commented out code
Eeems Mar 10, 2026
ff0d35d
Fix annotations
Eeems Mar 10, 2026
85619ee
Fix volume seek/read bugs
Eeems Mar 10, 2026
251038d
Fix bit shifting
Eeems Mar 11, 2026
446fa41
Fix ge's other branch
Eeems Mar 11, 2026
333f344
Fix expected_magic typing
Eeems Mar 11, 2026
248e0c2
Fix .. checking
Eeems Mar 11, 2026
15ba287
Fix seeking out of bounds
Eeems Mar 11, 2026
881a48c
Different cache per volume
Eeems Mar 11, 2026
03fcb02
Fix linting and caching
Eeems Mar 11, 2026
aa5a2d6
Get info_length from DXRoot
Eeems Mar 11, 2026
ab23778
Don't hide invalid dirents
Eeems Mar 11, 2026
13b37af
Clean up inode flag checking
Eeems Mar 11, 2026
55bc717
Fix is_inline vs has_extents
Eeems Mar 11, 2026
eb70910
Fix info_length
Eeems Mar 11, 2026
ce47952
Fix checksum calculation
Eeems Mar 11, 2026
459998a
Fix cache key
Eeems Mar 11, 2026
aacf101
Fix ExtentBlocks.__iter__
Eeems Mar 11, 2026
6a29c89
Stacklevel changes
Eeems Mar 11, 2026
764a162
Fix Directory._get_file_type()
Eeems Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ ${VENV_BIN_ACTIVATE}: requirements.txt
@echo "Setting up development virtual env in .venv"
$(PYTHON) -m venv .venv
. ${VENV_BIN_ACTIVATE}; \
$(PYTHON) -m pip install wheel build ruff; \
$(PYTHON) -m pip install \
wheel \
build \
ruff \
basedpyright; \
$(PYTHON) -m pip install \
-r requirements.txt

Expand All @@ -60,11 +64,13 @@ all: release

lint: $(VENV_BIN_ACTIVATE)
. $(VENV_BIN_ACTIVATE); \
$(PYTHON) -m ruff check
$(PYTHON) -m ruff check; \
$(PYTHON) -m basedpyright

lint-fix: $(VENV_BIN_ACTIVATE)
. $(VENV_BIN_ACTIVATE); \
$(PYTHON) -m ruff check --fix
$(PYTHON) -m ruff check --fix; \
$(PYTHON) -m basedpyright

format: $(VENV_BIN_ACTIVATE)
. $(VENV_BIN_ACTIVATE); \
Expand Down
13 changes: 11 additions & 2 deletions ext4/_compat.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import os
from typing import Protocol
from typing import runtime_checkable
from typing import TypeVar
from typing import Any

# Added in python 3.12
try:
from typing import override # pyright: ignore[reportAssignmentType]

except ImportError:
from typing import Callable
from typing import Any

def override(fn: Callable[..., Any]): # pyright: ignore[reportExplicitAny]
return fn
Expand All @@ -28,4 +29,12 @@ class PeekableStream(ReadableStream, Protocol):
def peek(self, size: int = 0, /) -> bytes: ...


__all__ = ["override", "ReadableStream"]
T = TypeVar("T")


def assert_cast(obj: Any, t: type[T], /) -> T: # pyright: ignore[reportExplicitAny, reportAny]
assert isinstance(obj, t), f"Object is: {type(obj)} not {t}" # pyright: ignore[reportAny]
return obj


__all__ = ["override", "ReadableStream", "PeekableStream", "assert_cast"]
32 changes: 11 additions & 21 deletions ext4/block.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# pyright: reportImportCycles=false
import io
import errno

from ._compat import override

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .inode import Inode


class BlockIOBlocks(object):
def __init__(self, blockio: "BlockIO"):
Expand All @@ -17,43 +23,27 @@ def block_size(self):
def volume(self):
return self.blockio.inode.volume

@property
def ee_start(self):
return self.blockio.ee_start

@property
def ee_block(self):
return self.blockio.ee_block

@property
def ee_len(self):
return self.blockio.ee_len

def __contains__(self, ee_block):
def __contains__(self, ee_block: int):
for extent in self.blockio.extents:
if ee_block in extent.blocks:
return True

return False

def __getitem__(self, ee_block):
def __getitem__(self, ee_block: int):
for extent in self.blockio.extents:
if ee_block not in extent.blocks:
continue

block = extent.blocks[ee_block]
if block is None:
break

return block
return extent.blocks[ee_block]

return self._null_block


class BlockIO(io.RawIOBase):
def __init__(self, inode):
def __init__(self, inode: "Inode"):
super().__init__()
self.inode = inode
self.inode: "Inode" = inode
self.cursor: int = 0
self.blocks: BlockIOBlocks = BlockIOBlocks(self)

Expand Down
102 changes: 66 additions & 36 deletions ext4/blockdescriptor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
# pyright: reportImportCycles=false
from ctypes import c_uint32
from ctypes import c_uint16

from typing import final
from typing import TYPE_CHECKING

from .enum import EXT4_BG
from .struct import Ext4Struct
from .struct import crc32c
from ._compat import assert_cast

if TYPE_CHECKING:
from .volume import Volume


@final
class BlockDescriptor(Ext4Struct):
_pack_ = 1
# _anonymous_ = ("bg_reserved",)
Expand Down Expand Up @@ -35,85 +44,105 @@ class BlockDescriptor(Ext4Struct):
("bg_reserved", c_uint32),
]

def __init__(self, volume, offset, bg_no):
def __init__(self, volume: "Volume", offset: int, bg_no: int):
super().__init__(volume, offset)
self.bg_no = bg_no
self.bg_no: int = bg_no

@property
def bg_block_bitmap(self):
def bg_block_bitmap(self) -> int:
bg_block_bitmap_lo = assert_cast(self.bg_block_bitmap_lo, int) # pyright: ignore[reportAny]
bg_block_bitmap_hi = assert_cast(self.bg_block_bitmap_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_block_bitmap_hi << 32 | self.bg_block_bitmap_lo
return bg_block_bitmap_hi << 32 | bg_block_bitmap_lo

return self.bg_block_bitmap_lo
return bg_block_bitmap_lo

@property
def bg_inode_bitmap(self):
def bg_inode_bitmap(self) -> int:
bg_inode_bitmap_lo = assert_cast(self.bg_inode_bitmap_lo, int) # pyright: ignore[reportAny]
bg_inode_bitmap_hi = assert_cast(self.bg_inode_bitmap_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_inode_bitmap_hi << 32 | self.bg_inode_bitmap_lo
return bg_inode_bitmap_hi << 32 | bg_inode_bitmap_lo

return self.bg_inode_bitmap_lo
return bg_inode_bitmap_lo

@property
def bg_free_blocks_count(self):
def bg_free_blocks_count(self) -> int:
bg_free_blocks_count_lo = assert_cast(self.bg_free_blocks_count_lo, int) # pyright: ignore[reportAny]
bg_free_blocks_count_hi = assert_cast(self.bg_free_blocks_count_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_free_blocks_count_hi << 32 | self.bg_free_blocks_count_lo
return bg_free_blocks_count_hi << 16 | bg_free_blocks_count_lo

return self.bg_free_blocks_count_lo
return bg_free_blocks_count_lo
Comment thread
Eeems marked this conversation as resolved.

@property
def bg_free_inodes_count(self):
def bg_free_inodes_count(self) -> int:
bg_free_inodes_count_lo = assert_cast(self.bg_free_inodes_count_lo, int) # pyright: ignore[reportAny]
bg_free_inodes_count_hi = assert_cast(self.bg_free_inodes_count_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_free_inodes_count_hi << 32 | self.bg_free_inodes_count_lo
return bg_free_inodes_count_hi << 16 | bg_free_inodes_count_lo

return self.bg_free_inodes_count_lo
return bg_free_inodes_count_lo

@property
def bg_exclude_bitmap(self):
def bg_exclude_bitmap(self) -> int:
bg_exclude_bitmap_lo = assert_cast(self.bg_exclude_bitmap_lo, int) # pyright: ignore[reportAny]
bg_exclude_bitmap_hi = assert_cast(self.bg_exclude_bitmap_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_exclude_bitmap_hi << 32 | self.bg_exclude_bitmap_lo
return bg_exclude_bitmap_hi << 32 | bg_exclude_bitmap_lo

return self.bg_exclude_bitmap_lo
return bg_exclude_bitmap_lo

@property
def bg_used_dirs_count(self):
def bg_used_dirs_count(self) -> int:
bg_used_dirs_count_lo = assert_cast(self.bg_used_dirs_count_lo, int) # pyright: ignore[reportAny]
bg_used_dirs_count_hi = assert_cast(self.bg_used_dirs_count_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_used_dirs_count_hi << 32 | self.bg_used_dirs_count_lo
return bg_used_dirs_count_hi << 16 | bg_used_dirs_count_lo

return self.bg_used_dirs_count_lo
return bg_used_dirs_count_lo

@property
def bg_block_bitmap_csum(self):
def bg_block_bitmap_csum(self) -> int:
bg_block_bitmap_csum_lo = assert_cast(self.bg_block_bitmap_csum_lo, int) # pyright: ignore[reportAny]
bg_block_bitmap_csum_hi = assert_cast(self.bg_block_bitmap_csum_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_block_bitmap_csum_hi << 32 | self.bg_block_bitmap_csum_lo
return bg_block_bitmap_csum_hi << 16 | bg_block_bitmap_csum_lo

return self.bg_block_bitmap_csum_lo
return bg_block_bitmap_csum_lo

@property
def bg_inode_bitmap_csum(self):
def bg_inode_bitmap_csum(self) -> int:
bg_inode_bitmap_csum_lo = assert_cast(self.bg_inode_bitmap_csum_lo, int) # pyright: ignore[reportAny]
bg_inode_bitmap_csum_hi = assert_cast(self.bg_inode_bitmap_csum_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_inode_bitmap_csum_hi << 32 | self.bg_inode_bitmap_csum_lo
return bg_inode_bitmap_csum_hi << 16 | bg_inode_bitmap_csum_lo

return self.bg_inode_bitmap_csum_lo
return bg_inode_bitmap_csum_lo

@property
def bg_itable_unused(self):
def bg_itable_unused(self) -> int:
bg_itable_unused_lo = assert_cast(self.bg_itable_unused_lo, int) # pyright: ignore[reportAny]
bg_itable_unused_hi = assert_cast(self.bg_itable_unused_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return self.bg_itable_unused_hi << 32 | self.bg_itable_unused_lo
return bg_itable_unused_hi << 16 | bg_itable_unused_lo

return self.bg_itable_unused_lo
return bg_itable_unused_lo

@property
def bg_inode_table(self):
def bg_inode_table(self) -> int:
bg_inode_table_lo = assert_cast(self.bg_inode_table_lo, int) # pyright: ignore[reportAny]
bg_inode_table_hi = assert_cast(self.bg_inode_table_hi, int) # pyright: ignore[reportAny]
if self.volume.has_hi:
return (self.bg_inode_table_hi << 32) + self.bg_inode_table_lo
return (bg_inode_table_hi << 32) + bg_inode_table_lo

return self.bg_inode_table_lo
return bg_inode_table_lo

@property
def superblock(self):
return self.volume.superblock

@property
@Ext4Struct.checksum.getter
def checksum(self):
csum = crc32c(self.bg_no.to_bytes(4, "little"), self.volume.seed)
csum = crc32c(bytes(self)[: BlockDescriptor.bg_checksum.offset], csum)
Expand All @@ -124,6 +153,7 @@ def checksum(self):
)
return csum & 0xFFFF

@property
def expected_checksum(self):
return self.bg_checksum
@Ext4Struct.expected_checksum.getter
def expected_checksum(self) -> int:
bg_checksum = assert_cast(self.bg_checksum, int) # pyright: ignore[reportAny]
return bg_checksum
Loading
Loading