diff --git a/_test_image.sh b/_test_image.sh index 4aee969..259afd2 100755 --- a/_test_image.sh +++ b/_test_image.sh @@ -28,6 +28,7 @@ trap "rm -r \"$tmp_dir\"" EXIT echo "[test] Using temporary directory: $tmp_dir" echo "[test] Generating files..." echo "hello world" >"$tmp_dir"/test.txt +ln -s test.txt "$tmp_dir"/symlink.txt for i in {1..1000}; do echo "echo "hello world" >>'$tmp_dir/test.txt'" done | xargs -P "$(nproc)" -I {} bash -c '{}' @@ -43,7 +44,7 @@ done | xargs -P "$(nproc)" -I {} bash -c '{}' mkimage test32 "$tmp_dir" 20 -O ^64bit mkimage test64 "$tmp_dir" 20 -O 64bit -rm -f "$tmp_dir"/test*.txt +rm -f "$tmp_dir"/test*.txt "$tmp_dir"/symlink.txt echo "[test] Generating files..." echo "[test] Making image test_htree..." diff --git a/ext4/block.py b/ext4/block.py index 07948d4..03c7e17 100644 --- a/ext4/block.py +++ b/ext4/block.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false import errno import io import os diff --git a/ext4/blockdescriptor.py b/ext4/blockdescriptor.py index cf87af3..2e686a9 100644 --- a/ext4/blockdescriptor.py +++ b/ext4/blockdescriptor.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false from ctypes import ( c_uint16, c_uint32, diff --git a/ext4/directory.py b/ext4/directory.py index 42b4782..c6f1508 100644 --- a/ext4/directory.py +++ b/ext4/directory.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false from ctypes import ( addressof, c_char, diff --git a/ext4/enum.py b/ext4/enum.py index aa11e24..3648f87 100644 --- a/ext4/enum.py +++ b/ext4/enum.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false from ctypes import ( c_uint8, c_uint16, diff --git a/ext4/extent.py b/ext4/extent.py index 397d5eb..148f4f6 100644 --- a/ext4/extent.py +++ b/ext4/extent.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false from collections.abc import Iterator from ctypes import ( c_uint16, diff --git a/ext4/htree.py b/ext4/htree.py index d418d95..afb8e60 100644 --- a/ext4/htree.py +++ b/ext4/htree.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false import warnings from collections.abc import Generator from ctypes import ( diff --git a/ext4/inode.py b/ext4/inode.py index ab2d52a..ad6a15d 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false from __future__ import annotations import errno @@ -74,6 +73,10 @@ class InodeError(Exception): pass +class MalformedInodeError(Exception): + pass + + @final class Linux1(LittleEndianStructure): _pack_ = 1 @@ -482,8 +485,22 @@ def open( class SymbolicLink(Inode): + @property + def is_fast_symlink(self) -> bool: + i_blocks_lo = assert_cast(self.i_blocks_lo, int) # pyright: ignore[reportAny] + return i_blocks_lo == 0 and not self.is_inline + def readlink(self) -> bytes: - return self._open().read() + if not self.is_fast_symlink: + return self._open().read() + + if self.i_size > Inode.i_block.size: + raise MalformedInodeError( + f"Fast symlink target too large: {self.i_size} > {Inode.i_block.size}" + ) + + _ = self.volume.seek(self.offset + Inode.i_block.offset) + return self.volume.read(self.i_size) class Directory(Inode): diff --git a/ext4/struct.py b/ext4/struct.py index 92e4883..1b80648 100644 --- a/ext4/struct.py +++ b/ext4/struct.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false import ctypes import errno import warnings diff --git a/ext4/superblock.py b/ext4/superblock.py index 635c54e..bae84c2 100644 --- a/ext4/superblock.py +++ b/ext4/superblock.py @@ -1,4 +1,3 @@ -# pyright: reportImportCycles=false from ctypes import ( c_ubyte, c_uint8, diff --git a/pyproject.toml b/pyproject.toml index bad0b07..aa19573 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ext4" -version = "1.3" +version = "1.3.1" authors = [ { name="Eeems", email="eeems@eeems.email" }, ] @@ -91,3 +91,4 @@ ignore = [ [tool.pyright] exclude = [".venv", "build"] reportMissingTypeStubs = false +reportImportCycles = false diff --git a/test.py b/test.py index 7f9011a..3aac1b5 100644 --- a/test.py +++ b/test.py @@ -28,8 +28,8 @@ def test_path_tuple(path: str | bytes, expected: tuple[bytes, ...]) -> None: except Exception as e: FAILED = True # pyright: ignore[reportConstantRedefinition] print("fail") - print(" ", end="") - print(e) + print(" ", end="", file=sys.stderr) + print(e, file=sys.stderr) def _eval_or_False(source: str) -> Any: # pyright: ignore[reportExplicitAny, reportAny] # noqa: ANN401 @@ -51,7 +51,7 @@ def _assert(source: str, debug: Callable[[], Any] | None = None) -> None: # pyr FAILED = True # pyright: ignore[reportConstantRedefinition] print("fail") if debug is not None: - print(f" {debug()}") + print(f" {debug()}", file=sys.stderr) def test_magic_error(f: BufferedReader) -> None: @@ -68,8 +68,8 @@ def test_magic_error(f: BufferedReader) -> None: except Exception as e: FAILED = True # pyright: ignore[reportConstantRedefinition] print("fail") - print(" ", end="") - print(e) + print(" ", end="", file=sys.stderr) + print(e, file=sys.stderr) def test_root_inode(volume: ext4.Volume) -> None: @@ -82,8 +82,8 @@ def test_root_inode(volume: ext4.Volume) -> None: except ext4.struct.ChecksumError as e: FAILED = True # pyright: ignore[reportConstantRedefinition] print("fail") - print(" ", end="") - print(e) + print(" ", end="", file=sys.stderr) + print(e, file=sys.stderr) print("check ext4.Volume stream validation: ", end="") @@ -98,8 +98,8 @@ def test_root_inode(volume: ext4.Volume) -> None: except Exception as e: FAILED = True # pyright: ignore[reportConstantRedefinition] print("fail") - print(" ", end="") - print(e) + print(" ", end="", file=sys.stderr) + print(e, file=sys.stderr) test_path_tuple("/", tuple()) test_path_tuple(b"/", tuple()) @@ -162,6 +162,10 @@ def test_root_inode(volume: ext4.Volume) -> None: _ = b.seek(0) _assert(f"b.read({x}) == {data[:x]}", lambda: b.seek(0) == 0 and b.read(x)) + inode = cast(ext4.SymbolicLink, volume.inode_at("/symlink.txt")) + _assert("isinstance(inode, ext4.SymbolicLink)") + _assert('inode.readlink() == b"test.txt"', inode.readlink) + img_file = "test_htree.ext4" print(f"Testing image: {img_file}") with open(img_file, "rb") as f: