From ef28b08e9a05a1c0e07f95f8bb7b910fd38d49d9 Mon Sep 17 00:00:00 2001 From: Roel de Jong <12800443+twiggler@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:05:02 +0200 Subject: [PATCH 1/4] Support block size and number of blocks in stat output --- dissect/target/filesystems/jffs.py | 5 ++++ tests/filesystems/test_jffs2.py | 47 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/filesystems/test_jffs2.py diff --git a/dissect/target/filesystems/jffs.py b/dissect/target/filesystems/jffs.py index c6e2a4819..8d1e91551 100644 --- a/dissect/target/filesystems/jffs.py +++ b/dissect/target/filesystems/jffs.py @@ -119,4 +119,9 @@ def lstat(self) -> fsutil.stat_result: ] ) + # JFFS2 block size is a function of the "erase size" of the underlying flash device. + # Linux stat reports the default block size, which is defined as 4k in libc. + st_info.st_blksize = 4096 + st_info.st_blocks = (node.isize + 511) // 512 if node.isize else 0 + return st_info diff --git a/tests/filesystems/test_jffs2.py b/tests/filesystems/test_jffs2.py new file mode 100644 index 000000000..fee7845a5 --- /dev/null +++ b/tests/filesystems/test_jffs2.py @@ -0,0 +1,47 @@ +from datetime import datetime +from typing import Iterator +from unittest.mock import Mock, patch +import pytest + +from dissect.target.filesystems.jffs import JFFSFilesystem, JFFSFilesystemEntry + + +@pytest.fixture +def jffs_fs() -> Iterator[JFFSFilesystem]: + with patch("dissect.jffs.jffs2.JFFS2"): + jffs_fs = JFFSFilesystem(Mock()) + yield jffs_fs + + +@pytest.fixture +def jffs_fs_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: + raw_inode = Mock(uid=1000, guid=999, isize=165002) + inode = Mock( + mode=33204, + inum=4, + inode=raw_inode, + atime=datetime(2024, 10, 1, 12, 0, 0), + mtime=datetime(2024, 10, 2, 12, 0, 0), + ctime=datetime(2024, 10, 3, 12, 0, 0), + ) + + entry = JFFSFilesystemEntry(jffs_fs, "/some_file", inode) + yield entry + + +def test_jffs2(jffs_fs: JFFSFilesystem, jffs_fs_entry: JFFSFilesystemEntry) -> None: + stat = jffs_fs_entry.stat() + + entry = jffs_fs_entry.entry + assert stat.st_mode == entry.mode + assert stat.st_ino == entry.inum + assert stat.st_dev == id(jffs_fs) + assert stat.st_nlink == 1 + assert stat.st_uid == entry.inode.uid + assert stat.st_gid == entry.inode.gid + assert stat.st_size == entry.inode.isize + assert stat.st_atime == entry.atime.timestamp() + assert stat.st_mtime == entry.mtime.timestamp() + assert stat.st_ctime == entry.ctime.timestamp() + assert stat.st_blksize == 4096 + assert stat.st_blocks == 323 From 7543c0d1237d9dc6ce3fecb4a79668dac0762741 Mon Sep 17 00:00:00 2001 From: Roel de Jong <12800443+twiggler@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:37:13 +0200 Subject: [PATCH 2/4] Return 0 blocks for symlinks and directories --- dissect/target/filesystems/jffs.py | 2 +- tests/filesystems/test_jffs2.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dissect/target/filesystems/jffs.py b/dissect/target/filesystems/jffs.py index 8d1e91551..2d0122940 100644 --- a/dissect/target/filesystems/jffs.py +++ b/dissect/target/filesystems/jffs.py @@ -122,6 +122,6 @@ def lstat(self) -> fsutil.stat_result: # JFFS2 block size is a function of the "erase size" of the underlying flash device. # Linux stat reports the default block size, which is defined as 4k in libc. st_info.st_blksize = 4096 - st_info.st_blocks = (node.isize + 511) // 512 if node.isize else 0 + st_info.st_blocks = (node.isize + 511) // 512 if self.is_file() else 0 return st_info diff --git a/tests/filesystems/test_jffs2.py b/tests/filesystems/test_jffs2.py index fee7845a5..c3fbc90f9 100644 --- a/tests/filesystems/test_jffs2.py +++ b/tests/filesystems/test_jffs2.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Iterator from unittest.mock import Mock, patch + import pytest from dissect.target.filesystems.jffs import JFFSFilesystem, JFFSFilesystemEntry From c572d675b2276b09f362cfc8651cb06c0bbc8280 Mon Sep 17 00:00:00 2001 From: Roel de Jong <12800443+twiggler@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:30:47 +0200 Subject: [PATCH 3/4] Add test for directory entry --- tests/filesystems/test_jffs2.py | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/tests/filesystems/test_jffs2.py b/tests/filesystems/test_jffs2.py index c3fbc90f9..993fb00ac 100644 --- a/tests/filesystems/test_jffs2.py +++ b/tests/filesystems/test_jffs2.py @@ -15,7 +15,7 @@ def jffs_fs() -> Iterator[JFFSFilesystem]: @pytest.fixture -def jffs_fs_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: +def jffs_fs_file_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: raw_inode = Mock(uid=1000, guid=999, isize=165002) inode = Mock( mode=33204, @@ -24,19 +24,45 @@ def jffs_fs_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: atime=datetime(2024, 10, 1, 12, 0, 0), mtime=datetime(2024, 10, 2, 12, 0, 0), ctime=datetime(2024, 10, 3, 12, 0, 0), + is_file=lambda: True, + is_dir=lambda: False, + is_symlink=lambda: False, ) entry = JFFSFilesystemEntry(jffs_fs, "/some_file", inode) yield entry -def test_jffs2(jffs_fs: JFFSFilesystem, jffs_fs_entry: JFFSFilesystemEntry) -> None: - stat = jffs_fs_entry.stat() +@pytest.fixture +def jffs_fs_directory_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: + raw_inode = Mock(uid=1000, guid=999, isize=0) + inode = Mock( + mode=16893, + inum=2, + inode=raw_inode, + atime=datetime(2024, 10, 1, 12, 0, 0), + mtime=datetime(2024, 10, 2, 12, 0, 0), + ctime=datetime(2024, 10, 3, 12, 0, 0), + is_file=lambda: False, + is_dir=lambda: True, + is_symlink=lambda: False, + ) + + entry = JFFSFilesystemEntry(jffs_fs, "/some_directory", inode) + yield entry + + +@pytest.mark.parametrize( + "entry_fixture, expected_blocks", [("jffs_fs_file_entry", 323), ("jffs_fs_directory_entry", 0)] +) +def test_jffs2_stat(entry_fixture: str, expected_blocks: int, request: pytest.FixtureRequest) -> None: + jffs_entry: JFFSFilesystemEntry = request.getfixturevalue(entry_fixture) + stat = jffs_entry.stat() - entry = jffs_fs_entry.entry + entry = jffs_entry.entry assert stat.st_mode == entry.mode assert stat.st_ino == entry.inum - assert stat.st_dev == id(jffs_fs) + assert stat.st_dev == id(jffs_entry.fs) assert stat.st_nlink == 1 assert stat.st_uid == entry.inode.uid assert stat.st_gid == entry.inode.gid @@ -45,4 +71,4 @@ def test_jffs2(jffs_fs: JFFSFilesystem, jffs_fs_entry: JFFSFilesystemEntry) -> N assert stat.st_mtime == entry.mtime.timestamp() assert stat.st_ctime == entry.ctime.timestamp() assert stat.st_blksize == 4096 - assert stat.st_blocks == 323 + assert stat.st_blocks == expected_blocks From 1b3935ffb95b10e43d33110452061d75f66dacb7 Mon Sep 17 00:00:00 2001 From: Roel de Jong <12800443+twiggler@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:13:20 +0200 Subject: [PATCH 4/4] Specify file mode in octal format --- tests/filesystems/test_jffs2.py | 4 ++-- tests/filesystems/test_xfs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/filesystems/test_jffs2.py b/tests/filesystems/test_jffs2.py index 993fb00ac..e9afa16f4 100644 --- a/tests/filesystems/test_jffs2.py +++ b/tests/filesystems/test_jffs2.py @@ -18,7 +18,7 @@ def jffs_fs() -> Iterator[JFFSFilesystem]: def jffs_fs_file_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: raw_inode = Mock(uid=1000, guid=999, isize=165002) inode = Mock( - mode=33204, + mode=0o100664, inum=4, inode=raw_inode, atime=datetime(2024, 10, 1, 12, 0, 0), @@ -37,7 +37,7 @@ def jffs_fs_file_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry] def jffs_fs_directory_entry(jffs_fs: JFFSFilesystem) -> Iterator[JFFSFilesystemEntry]: raw_inode = Mock(uid=1000, guid=999, isize=0) inode = Mock( - mode=16893, + mode=0o40775, inum=2, inode=raw_inode, atime=datetime(2024, 10, 1, 12, 0, 0), diff --git a/tests/filesystems/test_xfs.py b/tests/filesystems/test_xfs.py index 3d6bd2861..07428828e 100644 --- a/tests/filesystems/test_xfs.py +++ b/tests/filesystems/test_xfs.py @@ -25,7 +25,7 @@ def xfs_fs_entry(xfs_fs: XfsFilesystem) -> Iterator[XfsFilesystemEntry]: ctime = datetime(2024, 10, 3, 12, 0, 0) crtime = datetime(2024, 10, 4, 12, 0, 0) - dinode = Mock(di_mode=33272, di_nlink=1, di_uid=1000, di_gid=999) + dinode = Mock(di_mode=0o100750, di_nlink=1, di_uid=1000, di_gid=999) inode = Mock( nblocks=10, inode=dinode,