From 53860fdb0d8980e1ba207c3dbe8c449085b7d4a1 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Wed, 6 Aug 2025 13:48:24 +0800 Subject: [PATCH 01/21] fix superblock struct; fix extended attribute parse --- ext4/blockdescriptor.py | 2 +- ext4/directory.py | 2 +- ext4/inode.py | 12 +++++----- ext4/superblock.py | 10 ++++++++- ext4/volume.py | 2 +- ext4/xattr.py | 50 ++++++++++++++++++++++++++++++----------- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/ext4/blockdescriptor.py b/ext4/blockdescriptor.py index 363d214..a20721e 100644 --- a/ext4/blockdescriptor.py +++ b/ext4/blockdescriptor.py @@ -120,7 +120,7 @@ def checksum(self): if self.volume.has_hi: csum = crc32c(b"\x00\x00", csum) csum = crc32c( - bytes(self)[BlockDescriptor.bg_block_bitmap_hi.offset :], csum + bytes(self)[BlockDescriptor.bg_block_bitmap_hi.offset:], csum ) return csum & 0xFFFF diff --git a/ext4/directory.py b/ext4/directory.py index 707dadc..89dbd20 100644 --- a/ext4/directory.py +++ b/ext4/directory.py @@ -20,7 +20,7 @@ def __init__(self, directory, offset): super().__init__(directory.volume, offset) def read_from_volume(self): - data = self.directory._open().read()[self.offset : self.offset + self.size] + data = self.directory._open().read()[self.offset:self.offset + self.size] memmove(addressof(self), data, self.size) diff --git a/ext4/inode.py b/ext4/inode.py index ceb5cad..735b7e3 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -32,7 +32,7 @@ from .htree import DXRoot -from .xattr import ExtendedAttributeIBodyHeader +from .xattr import ExtendedAttributeIBodyHeader, ExtendedAttributeHeader class OpenDirectoryError(Exception): @@ -233,12 +233,12 @@ def checksum(self): csum = crc32c(data[:checksum_offset], self.seed) csum = crc32c(b"\0" * checksum_size, csum) csum = crc32c( - data[checksum_offset + checksum_size : self.EXT2_GOOD_OLD_INODE_SIZE], + data[checksum_offset + checksum_size:self.EXT2_GOOD_OLD_INODE_SIZE], csum, ) if self.has_hi: offset = Inode.i_checksum_hi.offset - csum = crc32c(data[self.EXT2_GOOD_OLD_INODE_SIZE : offset], csum) + csum = crc32c(data[self.EXT2_GOOD_OLD_INODE_SIZE:offset], csum) if self.fits_in_hi: csum = crc32c(b"\0" * Inode.i_checksum_hi.size, csum) offset += Inode.i_checksum_hi.size @@ -300,7 +300,7 @@ def open(self, mode="rb", encoding=None, newline=None): @property def xattrs(self): inline_offset = self.offset + self.EXT2_GOOD_OLD_INODE_SIZE + self.i_extra_isize - inline_size = self.superblock.s_inode_size - inline_offset + inline_size = self.offset + self.superblock.s_inode_size - inline_offset if inline_size > sizeof(ExtendedAttributeIBodyHeader): try: header = ExtendedAttributeIBodyHeader(self, inline_offset, inline_size) @@ -312,9 +312,9 @@ def xattrs(self): pass if self.i_file_acl != 0: - block_offset = self.i_file_acl * self.block_size + block_offset = self.i_file_acl * self.volume.block_size try: - header = ExtendedAttributeIBodyHeader( + header = ExtendedAttributeHeader( self, block_offset, self.volume.block_size ) header.verify() diff --git a/ext4/superblock.py b/ext4/superblock.py index 68ae72e..58eee88 100644 --- a/ext4/superblock.py +++ b/ext4/superblock.py @@ -76,6 +76,7 @@ class Superblock(Ext4Struct): ("s_desc_size", c_uint16), ("s_default_mount_opts", EXT4_DEFM), ("s_first_meta_bg", c_uint32), + ("s_mkfs_time", c_uint32), ("s_jnl_blocks", c_uint32 * 17), ("s_blocks_count_hi", c_uint32), ("s_r_blocks_count_hi", c_uint32), @@ -110,7 +111,7 @@ class Superblock(Ext4Struct): ("s_usr_quota_inum", c_uint32), ("s_grp_quota_inum", c_uint32), ("s_overhead_blocks", c_uint32), - ("s_backup_bgs", c_uint32), + ("s_backup_bgs", c_uint32 * 2), ("s_encrypt_algos", FS_ENCRYPTION_MODE * 4), ("s_encrypt_pw_salt", c_uint8 * 16), ("s_lpf_ino", c_uint32), @@ -183,3 +184,10 @@ def seed(self): return self.s_checksum_seed return crc32c(bytes(self.s_uuid)) + + @property + def desc_size(self): + if self.s_feature_incompat & EXT4_FEATURE_INCOMPAT.IS64BIT != 0: + return self.s_desc_size + else: + return 32 diff --git a/ext4/volume.py b/ext4/volume.py index 65d1344..d410113 100644 --- a/ext4/volume.py +++ b/ext4/volume.py @@ -83,7 +83,7 @@ def __init__( ): descriptor = BlockDescriptor( self, - table_offset + (index * self.superblock.s_desc_size), + table_offset + (index * self.superblock.desc_size), index, ) descriptor.verify() diff --git a/ext4/xattr.py b/ext4/xattr.py index af92c1b..2ae53af 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -7,7 +7,7 @@ from .struct import Ext4Struct from .struct import crc32c -from .enum import EXT4_FL +from .enum import EXT4_FL, EXT4_FEATURE_INCOMPAT class ExtendedAttributeError(Exception): @@ -15,8 +15,10 @@ class ExtendedAttributeError(Exception): class ExtendedAttributeBase(Ext4Struct): - def __init__(self, inode, offset, size): + def __init__(self, inode, offset, size=None): self.inode = inode + if size is None: + size = self.size self.data_size = size super().__init__(inode.volume, offset) @@ -40,21 +42,24 @@ def magic(self): def expected_magic(self): return 0xEA020000 + def value_offset(self, entry): + return entry.offset + entry.e_value_offs + def __iter__(self): offset = self.offset + (4 * ((sizeof(self) + 3) // 4)) i = 0 while i < self.data_size: - entry = ExtendedAttributeEntry(offset + i) + entry = ExtendedAttributeEntry(self.inode, offset + i) if ( entry.e_name_len | entry.e_name_index | entry.e_value_offs - | entry.e_value_inum + | entry.value_inum ) == 0: break - if entry.e_value_inum != 0: - inode = self.volue.inodes[entry.e_value_inum] + if entry.value_inum != 0: + inode = self.volume.inodes[entry.value_inum] if (inode.i_flags & EXT4_FL.EA_INODE) != 0: message = f"Inode {inode.i_no:d} is not marked as large extended attribute value" if not self.volume.ignore_flags: @@ -64,12 +69,18 @@ def __iter__(self): # TODO determine if e_value_size or i_size are required to limit results? value = inode.open().read() + elif entry.e_value_size != 0: + value_offset = self.value_offset(entry) + if value_offset + entry.e_value_size > self.offset + self.data_size: + value = b"" + else: + self.volume.seek(value_offset) + value = self.volume.read(entry.e_value_size) else: - self.volume.seek(offset + i + entry.e_value_offs) - self.volume.read(entry.e_value_size) + value = b"" yield entry.name_str, value - i += (entry.size + 3) // 4 + i += 4 * ((entry.size + 3) // 4) class ExtendedAttributeHeader(ExtendedAttributeIBodyHeader): @@ -80,7 +91,7 @@ class ExtendedAttributeHeader(ExtendedAttributeIBodyHeader): ("h_blocks", c_uint32), ("h_hash", c_uint32), ("h_checksum", c_uint32), - ("h_reserved", c_uint32 * 2), + ("h_reserved", c_uint32 * 3), ] def verify(self): @@ -91,6 +102,9 @@ def verify(self): f"{self.inode.i_no:d}: {self.h_blocks:d} (expected 1)" ) + def value_offset(self, entry): + return self.offset + entry.e_value_offs + @property def expected_checksum(self): if not self.h_checksum: @@ -117,6 +131,7 @@ class ExtendedAttributeEntry(ExtendedAttributeBase): "system.posix_acl_access", "system.posix_acl_default", "trusted.", + "", "security.", "system.", "system.richacl", @@ -143,13 +158,22 @@ def size(self): @property def name_str(self): - if 0 > self.e_name_index or self.e_name_index > len( + name_index = self.e_name_index + if 0 > name_index or name_index >= len( ExtendedAttributeEntry.NAME_INDICES ): - raise ExtendedAttributeError( + warnings.warn( f"Unknown attribute prefix {self.e_name_index:d}" ) + name_index = 0 return ExtendedAttributeEntry.NAME_INDICES[ - self.e_name_index + name_index ] + self.e_name.decode("iso-8859-2") + + @property + def value_inum(self): + if (self.volume.superblock.s_feature_incompat & EXT4_FEATURE_INCOMPAT.EA_INODE) != 0: + return self.e_value_inum + else: + return 0 From 9151cb5b57a631dda89f077c7986b68e18e13622 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Wed, 6 Aug 2025 13:55:55 +0800 Subject: [PATCH 02/21] fix size --- ext4/xattr.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ext4/xattr.py b/ext4/xattr.py index 2ae53af..9cf6163 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -15,10 +15,8 @@ class ExtendedAttributeError(Exception): class ExtendedAttributeBase(Ext4Struct): - def __init__(self, inode, offset, size=None): + def __init__(self, inode, offset, size): self.inode = inode - if size is None: - size = self.size self.data_size = size super().__init__(inode.volume, offset) @@ -49,7 +47,7 @@ def __iter__(self): offset = self.offset + (4 * ((sizeof(self) + 3) // 4)) i = 0 while i < self.data_size: - entry = ExtendedAttributeEntry(self.inode, offset + i) + entry = ExtendedAttributeEntry(self.inode, offset + i, self.data_size - i) if ( entry.e_name_len | entry.e_name_index From 86b2abe0e488f25666b0dbeb2f62ba04e6c6786c Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Wed, 6 Aug 2025 22:15:41 +0800 Subject: [PATCH 03/21] Update ext4/inode.py Co-authored-by: Nathaniel van Diepen --- ext4/inode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext4/inode.py b/ext4/inode.py index 735b7e3..c649deb 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -32,7 +32,8 @@ from .htree import DXRoot -from .xattr import ExtendedAttributeIBodyHeader, ExtendedAttributeHeader +from .xattr import ExtendedAttributeIBodyHeader +from .xattr import ExtendedAttributeHeader class OpenDirectoryError(Exception): From d9aa378727948b93ad21b319e0d892a67da281d0 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Wed, 6 Aug 2025 22:15:59 +0800 Subject: [PATCH 04/21] Update ext4/xattr.py Co-authored-by: Nathaniel van Diepen --- ext4/xattr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext4/xattr.py b/ext4/xattr.py index 9cf6163..ebea240 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -7,7 +7,8 @@ from .struct import Ext4Struct from .struct import crc32c -from .enum import EXT4_FL, EXT4_FEATURE_INCOMPAT +from .enum import EXT4_FL +from .enum import EXT4_FEATURE_INCOMPAT class ExtendedAttributeError(Exception): From 30461bd76b2e12b9be12af112890c95583cbbf91 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Thu, 7 Aug 2025 10:04:51 +0800 Subject: [PATCH 05/21] move block_size to inode --- ext4/inode.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ext4/inode.py b/ext4/inode.py index c649deb..4364868 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -193,6 +193,10 @@ def __init__(self, volume, offset, i_no): def superblock(self): return self.volume.superblock + @property + def block_size(self): + return self.volume.block_size + @property def i_size(self): return self.i_size_high << 32 | self.i_size_lo @@ -313,10 +317,10 @@ def xattrs(self): pass if self.i_file_acl != 0: - block_offset = self.i_file_acl * self.volume.block_size + block_offset = self.i_file_acl * self.block_size try: header = ExtendedAttributeHeader( - self, block_offset, self.volume.block_size + self, block_offset, self.block_size ) header.verify() for name, value in header: @@ -371,14 +375,6 @@ def validate(self): super().validate() # TODO validate each directory entry block with DirectoryEntryTail - @property - def superblock(self): - return self.volume.superblock - - @property - def block_size(self): - return self.volume.block_size - @property def has_filetype(self): return self.superblock.s_feature_incompat & EXT4_FEATURE_INCOMPAT.FILETYPE != 0 From 9a6528ff435d8150250daeb7108f01d0c4eda366 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Thu, 7 Aug 2025 10:29:49 +0800 Subject: [PATCH 06/21] add ignore_attrerror --- ext4/volume.py | 2 ++ ext4/xattr.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ext4/volume.py b/ext4/volume.py index d410113..506def1 100644 --- a/ext4/volume.py +++ b/ext4/volume.py @@ -61,6 +61,7 @@ def __init__( ignore_flags=False, ignore_magic=False, ignore_checksum=False, + ignore_attr_name_index=False, ): if not isinstance(stream, io.RawIOBase) and not isinstance( stream, io.BufferedIOBase @@ -73,6 +74,7 @@ def __init__( self.ignore_flags = ignore_flags self.ignore_magic = ignore_magic self.ignore_checksum = ignore_checksum + self.ignore_attr_name_index = ignore_attr_name_index self.superblock = Superblock(self) self.superblock.verify() self.group_descriptors = [] diff --git a/ext4/xattr.py b/ext4/xattr.py index ebea240..06041c0 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -161,10 +161,12 @@ def name_str(self): if 0 > name_index or name_index >= len( ExtendedAttributeEntry.NAME_INDICES ): - warnings.warn( - f"Unknown attribute prefix {self.e_name_index:d}" - ) - name_index = 0 + msg = f"Unknown attribute prefix {self.e_name_index:d}" + if self.volume.ignore_attr_name_index: + warnings.warn(msg, RuntimeWarning) + name_index = 0 + else: + raise ExtendedAttributeError(msg) return ExtendedAttributeEntry.NAME_INDICES[ name_index From 023f802307f93fcd9837fdbe33505592a26e4c96 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Fri, 8 Aug 2025 14:28:11 +0800 Subject: [PATCH 07/21] add attr test --- _test_image.sh | 12 ++++++++---- ext4/xattr.py | 2 +- test.py | 7 +++++++ test.sh | 1 - 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/_test_image.sh b/_test_image.sh index 3d4b93a..5134a60 100755 --- a/_test_image.sh +++ b/_test_image.sh @@ -2,12 +2,16 @@ set -e tmp_dir=$(mktemp -d) -trap "rm -r \"$tmp_dir\"" EXIT echo "hello world" > "$tmp_dir"/test.txt for i in {1..100};do - echo "hello world" >> "$tmp_dir"/test.txt + echo "hello world$i" >> "$tmp_dir"/test$i.txt + for j in {1..20};do + setfattr -n user.name$j -v value${i}_$j "$tmp_dir"/test$i.txt + done done -dd if=/dev/zero of=test.ext4.tmp count=1024 bs=1024 -mkfs.ext4 test.ext4.tmp -d "$tmp_dir" +trap "rm -r test.ext4{,.tmp}" EXIT +dd if=/dev/zero of=test.ext4.tmp count=20 bs=1048576 +mkfs.ext4 -g 1024 test.ext4.tmp -d "$tmp_dir" +trap "rm -r \"$tmp_dir\"" EXIT echo -n F > test.ext4 cat test.ext4.tmp >> test.ext4 diff --git a/ext4/xattr.py b/ext4/xattr.py index 06041c0..b42d05c 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -42,7 +42,7 @@ def expected_magic(self): return 0xEA020000 def value_offset(self, entry): - return entry.offset + entry.e_value_offs + return self.offset + sizeof(self) + entry.e_value_offs def __iter__(self): offset = self.offset + (4 * ((sizeof(self) + 3) // 4)) diff --git a/test.py b/test.py index 995cea8..ab2639d 100644 --- a/test.py +++ b/test.py @@ -73,6 +73,13 @@ def _assert(source: str): _assert("b.seek(1) == 1") _assert("b.seek(0) == 0") _assert("b.seek(10) == 10") + for i in range(1, 101): + inode = volume.inode_at(f"/test{i}.txt") + attrs = {k: v for k, v in inode.xattrs} + for j in range(1, 21): + _assert(f'attrs["user.name{j}"] == b"value{i}_{j}"') + data = inode.open().read() + _assert(f'data == b"hello world{i}\\n"') if FAILED: sys.exit(1) diff --git a/test.sh b/test.sh index 51c5b15..a8df76d 100755 --- a/test.sh +++ b/test.sh @@ -15,6 +15,5 @@ python -m pip install \ -r requirements.txt if ! [ -f test.ext4 ] || ! [ -f test.ext4.tmp ];then ./_test_image.sh - trap "rm -f test.ext4{,.tmp}" EXIT fi python test.py From af2ca988d51d7b8c8ed7a44f46abeab65581f81f Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Fri, 8 Aug 2025 14:44:15 +0800 Subject: [PATCH 08/21] add 32bit 64bit test --- _test_image.sh | 14 ++++++---- test.py | 73 +++++++++++++++++++++++++------------------------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/_test_image.sh b/_test_image.sh index 5134a60..c734887 100755 --- a/_test_image.sh +++ b/_test_image.sh @@ -9,9 +9,13 @@ for i in {1..100};do setfattr -n user.name$j -v value${i}_$j "$tmp_dir"/test$i.txt done done -trap "rm -r test.ext4{,.tmp}" EXIT -dd if=/dev/zero of=test.ext4.tmp count=20 bs=1048576 -mkfs.ext4 -g 1024 test.ext4.tmp -d "$tmp_dir" +trap "rm -r test{32,64}.ext4{,.tmp}" EXIT +dd if=/dev/zero of=test32.ext4.tmp count=20 bs=1048576 +dd if=/dev/zero of=test64.ext4.tmp count=20 bs=1048576 +mkfs.ext4 -g 1024 -O 64bit test64.ext4.tmp -d "$tmp_dir" +mkfs.ext4 -g 1024 -O ^64bit test32.ext4.tmp -d "$tmp_dir" trap "rm -r \"$tmp_dir\"" EXIT -echo -n F > test.ext4 -cat test.ext4.tmp >> test.ext4 +echo -n F > test32.ext4 +cat test32.ext4.tmp >> test32.ext4 +echo -n F > test64.ext4 +cat test64.ext4.tmp >> test64.ext4 diff --git a/test.py b/test.py index ab2639d..4377f23 100644 --- a/test.py +++ b/test.py @@ -44,42 +44,43 @@ def _assert(source: str): test_path_tuple("/test/test", (b"test", b"test")) test_path_tuple(b"/test/test", (b"test", b"test")) -offset = os.path.getsize("test.ext4") - os.path.getsize("test.ext4.tmp") -_assert("offset > 0") -with open("test.ext4", "rb") as f: - try: - print("check MagicError: ", end="") - _ = ext4.Volume(f, offset=0) - FAILED = True - print("fail") - print(" MagicError not raised") - except ext4.struct.MagicError: - print("pass") - - except Exception as e: - FAILED = True - print("fail") - print(" ", end="") - print(e) - - # Extract specific file - volume = ext4.Volume(f, offset=offset) - inode = cast(ext4.File, volume.inode_at("/test.txt")) - _assert("isinstance(inode, ext4.File)") - b = inode.open() - _assert("isinstance(b, ext4.BlockIO)") - _assert("b.readable()") - _assert("b.seekable()") - _assert("b.seek(1) == 1") - _assert("b.seek(0) == 0") - _assert("b.seek(10) == 10") - for i in range(1, 101): - inode = volume.inode_at(f"/test{i}.txt") - attrs = {k: v for k, v in inode.xattrs} - for j in range(1, 21): - _assert(f'attrs["user.name{j}"] == b"value{i}_{j}"') - data = inode.open().read() - _assert(f'data == b"hello world{i}\\n"') +for img_file in ("test32.ext4", "test64.ext4"): + offset = os.path.getsize(img_file) - os.path.getsize(f"{img_file}.tmp") + _assert("offset > 0") + with open(img_file, "rb") as f: + try: + print("check MagicError: ", end="") + _ = ext4.Volume(f, offset=0) + FAILED = True + print("fail") + print(" MagicError not raised") + except ext4.struct.MagicError: + print("pass") + + except Exception as e: + FAILED = True + print("fail") + print(" ", end="") + print(e) + + # Extract specific file + volume = ext4.Volume(f, offset=offset) + inode = cast(ext4.File, volume.inode_at("/test.txt")) + _assert("isinstance(inode, ext4.File)") + b = inode.open() + _assert("isinstance(b, ext4.BlockIO)") + _assert("b.readable()") + _assert("b.seekable()") + _assert("b.seek(1) == 1") + _assert("b.seek(0) == 0") + _assert("b.seek(10) == 10") + for i in range(1, 101): + inode = volume.inode_at(f"/test{i}.txt") + attrs = {k: v for k, v in inode.xattrs} + for j in range(1, 21): + _assert(f'attrs["user.name{j}"] == b"value{i}_{j}"') + data = inode.open().read() + _assert(f'data == b"hello world{i}\\n"') if FAILED: sys.exit(1) From 07f4e74381fe66836379c085a7dc2c4c8fb194df Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Fri, 8 Aug 2025 14:46:07 +0800 Subject: [PATCH 09/21] fix rm --- _test_image.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_test_image.sh b/_test_image.sh index c734887..328f5cd 100755 --- a/_test_image.sh +++ b/_test_image.sh @@ -2,6 +2,7 @@ set -e tmp_dir=$(mktemp -d) +trap "rm -r \"$tmp_dir\"" EXIT echo "hello world" > "$tmp_dir"/test.txt for i in {1..100};do echo "hello world$i" >> "$tmp_dir"/test$i.txt @@ -9,12 +10,11 @@ for i in {1..100};do setfattr -n user.name$j -v value${i}_$j "$tmp_dir"/test$i.txt done done -trap "rm -r test{32,64}.ext4{,.tmp}" EXIT +rm -r test{32,64}.ext4{,.tmp} dd if=/dev/zero of=test32.ext4.tmp count=20 bs=1048576 dd if=/dev/zero of=test64.ext4.tmp count=20 bs=1048576 mkfs.ext4 -g 1024 -O 64bit test64.ext4.tmp -d "$tmp_dir" mkfs.ext4 -g 1024 -O ^64bit test32.ext4.tmp -d "$tmp_dir" -trap "rm -r \"$tmp_dir\"" EXIT echo -n F > test32.ext4 cat test32.ext4.tmp >> test32.ext4 echo -n F > test64.ext4 From 72a7e340b820939e71ecd45d5bebf8df7618b792 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Fri, 8 Aug 2025 23:02:41 +0800 Subject: [PATCH 10/21] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=5Ftest=5Fimage.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _test_image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_test_image.sh b/_test_image.sh index 328f5cd..a9b40a7 100755 --- a/_test_image.sh +++ b/_test_image.sh @@ -5,12 +5,12 @@ tmp_dir=$(mktemp -d) trap "rm -r \"$tmp_dir\"" EXIT echo "hello world" > "$tmp_dir"/test.txt for i in {1..100};do + echo "hello world" >> "$tmp_dir"/test.txt echo "hello world$i" >> "$tmp_dir"/test$i.txt for j in {1..20};do setfattr -n user.name$j -v value${i}_$j "$tmp_dir"/test$i.txt done done -rm -r test{32,64}.ext4{,.tmp} dd if=/dev/zero of=test32.ext4.tmp count=20 bs=1048576 dd if=/dev/zero of=test64.ext4.tmp count=20 bs=1048576 mkfs.ext4 -g 1024 -O 64bit test64.ext4.tmp -d "$tmp_dir" From 889e5f10fe750f685665bf115d1d9296afa962f2 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Fri, 8 Aug 2025 23:06:28 +0800 Subject: [PATCH 11/21] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20test.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test.sh b/test.sh index a8df76d..af40b4e 100755 --- a/test.sh +++ b/test.sh @@ -13,7 +13,8 @@ python -m pip install wheel python -m pip install \ --extra-index-url=https://wheels.eeems.codes/ \ -r requirements.txt -if ! [ -f test.ext4 ] || ! [ -f test.ext4.tmp ];then +if ! [ -f test32.ext4 ] || ! [ -f test32.ext4.tmp ] || ! [ -f test32.ext4 ] || ! [ -f test32.ext4.tmp ] ;then ./_test_image.sh + trap "rm -f test{32,64}.ext4{,.tmp}" EXIT fi python test.py From 074f8c4ddd0741373e980b42992277158a269e25 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Mon, 11 Aug 2025 10:17:10 +0800 Subject: [PATCH 12/21] build --- .github/workflows/build.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e6320a3..d3675f5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -24,8 +24,10 @@ jobs: with: name: test.ext4 path: | - test.ext4 - test.ext4.tmp + test32.ext4 + test32.ext4.tmp + test64.ext4 + test64.ext4.tmp if-no-files-found: error test: name: Test on ${{ matrix.os }} python ${{ matrix.python }} @@ -50,7 +52,7 @@ jobs: - name: Download test.ext4 uses: actions/download-artifact@v4 with: - name: test.ext4 + pattern: test* path: . - name: Setup Python uses: actions/setup-python@v5 From 26a0402a9bbf6041e806c34adc9a8f7ce73bd72e Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Mon, 11 Aug 2025 10:21:57 +0800 Subject: [PATCH 13/21] stream --- _test_image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_test_image.sh b/_test_image.sh index a9b40a7..c7e1fea 100755 --- a/_test_image.sh +++ b/_test_image.sh @@ -6,7 +6,7 @@ trap "rm -r \"$tmp_dir\"" EXIT echo "hello world" > "$tmp_dir"/test.txt for i in {1..100};do echo "hello world" >> "$tmp_dir"/test.txt - echo "hello world$i" >> "$tmp_dir"/test$i.txt + echo "hello world$i" > "$tmp_dir"/test$i.txt for j in {1..20};do setfattr -n user.name$j -v value${i}_$j "$tmp_dir"/test$i.txt done From 17275470d35810b0da501ac8c1129ba5e50ff8df Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Mon, 11 Aug 2025 10:45:55 +0800 Subject: [PATCH 14/21] merge-multiple --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d3675f5..8100c00 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -53,6 +53,7 @@ jobs: uses: actions/download-artifact@v4 with: pattern: test* + merge-multiple: true path: . - name: Setup Python uses: actions/setup-python@v5 From 576457ceb8a138a1d877b9c2eeb346e00c7b7841 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Tue, 12 Aug 2025 13:41:40 +0800 Subject: [PATCH 15/21] Update .github/workflows/build.yaml Co-authored-by: Nathaniel van Diepen --- .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 8100c00..20be7a9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -52,7 +52,7 @@ jobs: - name: Download test.ext4 uses: actions/download-artifact@v4 with: - pattern: test* + name: test.ext4 merge-multiple: true path: . - name: Setup Python From fc5c1a32fec65381a591c0652a9887d0f8aaf9c6 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 16 Aug 2025 11:45:07 -0600 Subject: [PATCH 16/21] Update test.sh --- test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.sh b/test.sh index af40b4e..a688dc9 100755 --- a/test.sh +++ b/test.sh @@ -13,7 +13,7 @@ python -m pip install wheel python -m pip install \ --extra-index-url=https://wheels.eeems.codes/ \ -r requirements.txt -if ! [ -f test32.ext4 ] || ! [ -f test32.ext4.tmp ] || ! [ -f test32.ext4 ] || ! [ -f test32.ext4.tmp ] ;then +if ! [ -f test32.ext4 ] || ! [ -f test32.ext4.tmp ] || ! [ -f test64.ext4 ] || ! [ -f test64.ext4.tmp ] ;then ./_test_image.sh trap "rm -f test{32,64}.ext4{,.tmp}" EXIT fi From 96b2bc955b118d7c478fc11c40f525d66c45dc5c Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 16 Aug 2025 11:45:36 -0600 Subject: [PATCH 17/21] Update build.yaml --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 20be7a9..54f2f52 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -53,7 +53,6 @@ jobs: uses: actions/download-artifact@v4 with: name: test.ext4 - merge-multiple: true path: . - name: Setup Python uses: actions/setup-python@v5 From 112743e5df216857ab43918c5ec73dd742a11acf Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 16 Aug 2025 11:52:10 -0600 Subject: [PATCH 18/21] Format fix --- ext4/blockdescriptor.py | 2 +- ext4/directory.py | 2 +- ext4/inode.py | 8 +++----- ext4/superblock.py | 4 ++-- ext4/xattr.py | 14 +++++++------- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/ext4/blockdescriptor.py b/ext4/blockdescriptor.py index a20721e..363d214 100644 --- a/ext4/blockdescriptor.py +++ b/ext4/blockdescriptor.py @@ -120,7 +120,7 @@ def checksum(self): if self.volume.has_hi: csum = crc32c(b"\x00\x00", csum) csum = crc32c( - bytes(self)[BlockDescriptor.bg_block_bitmap_hi.offset:], csum + bytes(self)[BlockDescriptor.bg_block_bitmap_hi.offset :], csum ) return csum & 0xFFFF diff --git a/ext4/directory.py b/ext4/directory.py index 89dbd20..707dadc 100644 --- a/ext4/directory.py +++ b/ext4/directory.py @@ -20,7 +20,7 @@ def __init__(self, directory, offset): super().__init__(directory.volume, offset) def read_from_volume(self): - data = self.directory._open().read()[self.offset:self.offset + self.size] + data = self.directory._open().read()[self.offset : self.offset + self.size] memmove(addressof(self), data, self.size) diff --git a/ext4/inode.py b/ext4/inode.py index 4364868..62ec84d 100644 --- a/ext4/inode.py +++ b/ext4/inode.py @@ -238,12 +238,12 @@ def checksum(self): csum = crc32c(data[:checksum_offset], self.seed) csum = crc32c(b"\0" * checksum_size, csum) csum = crc32c( - data[checksum_offset + checksum_size:self.EXT2_GOOD_OLD_INODE_SIZE], + data[checksum_offset + checksum_size : self.EXT2_GOOD_OLD_INODE_SIZE], csum, ) if self.has_hi: offset = Inode.i_checksum_hi.offset - csum = crc32c(data[self.EXT2_GOOD_OLD_INODE_SIZE:offset], csum) + csum = crc32c(data[self.EXT2_GOOD_OLD_INODE_SIZE : offset], csum) if self.fits_in_hi: csum = crc32c(b"\0" * Inode.i_checksum_hi.size, csum) offset += Inode.i_checksum_hi.size @@ -319,9 +319,7 @@ def xattrs(self): if self.i_file_acl != 0: block_offset = self.i_file_acl * self.block_size try: - header = ExtendedAttributeHeader( - self, block_offset, self.block_size - ) + header = ExtendedAttributeHeader(self, block_offset, self.block_size) header.verify() for name, value in header: yield name, value diff --git a/ext4/superblock.py b/ext4/superblock.py index 58eee88..b4e3bd6 100644 --- a/ext4/superblock.py +++ b/ext4/superblock.py @@ -189,5 +189,5 @@ def seed(self): def desc_size(self): if self.s_feature_incompat & EXT4_FEATURE_INCOMPAT.IS64BIT != 0: return self.s_desc_size - else: - return 32 + + return 32 diff --git a/ext4/xattr.py b/ext4/xattr.py index b42d05c..cbfb074 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -158,9 +158,7 @@ def size(self): @property def name_str(self): name_index = self.e_name_index - if 0 > name_index or name_index >= len( - ExtendedAttributeEntry.NAME_INDICES - ): + if 0 > name_index or name_index >= len(ExtendedAttributeEntry.NAME_INDICES): msg = f"Unknown attribute prefix {self.e_name_index:d}" if self.volume.ignore_attr_name_index: warnings.warn(msg, RuntimeWarning) @@ -168,13 +166,15 @@ def name_str(self): else: raise ExtendedAttributeError(msg) - return ExtendedAttributeEntry.NAME_INDICES[ - name_index - ] + self.e_name.decode("iso-8859-2") + return ExtendedAttributeEntry.NAME_INDICES[name_index] + self.e_name.decode( + "iso-8859-2" + ) @property def value_inum(self): - if (self.volume.superblock.s_feature_incompat & EXT4_FEATURE_INCOMPAT.EA_INODE) != 0: + if ( + self.volume.superblock.s_feature_incompat & EXT4_FEATURE_INCOMPAT.EA_INODE + ) != 0: return self.e_value_inum else: return 0 From 69cc16d81195cc49e28604aaeca7b33c51bcc39b Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 16 Aug 2025 11:58:17 -0600 Subject: [PATCH 19/21] Add missing package --- .github/workflows/build.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index be9de06..f4fa851 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,6 +29,11 @@ jobs: steps: - name: Checkout the Git repository uses: actions/checkout@v4 + - name: Install dependencies + shell: bash + run: | + set -e + sudo apt-get install -y attr - name: Generate test.ext4 shell: bash run: | From 8c765c31c1dc46a8585f3ca80c98cbf763685033 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 16 Aug 2025 12:00:38 -0600 Subject: [PATCH 20/21] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 53e6e3e..ee437b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ext4" -version = "1.1.1" +version = "1.2" authors = [ { name="Eeems", email="eeems@eeems.email" }, ] From 20f058664231e770ca077d02a2a463c7b3f7a7c8 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Sat, 16 Aug 2025 12:14:43 -0600 Subject: [PATCH 21/21] explicit override --- ext4/xattr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext4/xattr.py b/ext4/xattr.py index cbfb074..005a7d2 100644 --- a/ext4/xattr.py +++ b/ext4/xattr.py @@ -1,3 +1,4 @@ +from typing import override import warnings from ctypes import c_uint32 @@ -101,6 +102,7 @@ def verify(self): f"{self.inode.i_no:d}: {self.h_blocks:d} (expected 1)" ) + @override def value_offset(self, entry): return self.offset + entry.e_value_offs