diff --git a/dissect/target/filesystems/ffs.py b/dissect/target/filesystems/ffs.py index 35158c96d..c0912dc42 100644 --- a/dissect/target/filesystems/ffs.py +++ b/dissect/target/filesystems/ffs.py @@ -126,6 +126,12 @@ def lstat(self) -> fsutil.stat_result: ] ) + # Note: stat on linux always returns the default block size of 4096 + # We are returning the actual block size of the filesystem, as on BSD + st_info.st_blksize = self.fs.ffs.block_size + # Note: st_blocks * 512 can be lower than st_blksize because FFS employs fragments + st_info.st_blocks = self.entry.nblocks + # Set the nanosecond resolution separately st_info.st_atime_ns = self.entry.atime_ns st_info.st_mtime_ns = self.entry.mtime_ns @@ -134,5 +140,6 @@ def lstat(self) -> fsutil.stat_result: # FFS2 has a birth time, FFS1 does not if btime := self.entry.btime: st_info.st_birthtime = btime.timestamp() + st_info.st_birthtime_ns = self.entry.btime_ns return st_info diff --git a/tests/filesystems/test_ffs.py b/tests/filesystems/test_ffs.py new file mode 100644 index 000000000..f22051052 --- /dev/null +++ b/tests/filesystems/test_ffs.py @@ -0,0 +1,67 @@ +from datetime import datetime +from typing import Iterator +from unittest.mock import Mock, patch + +import pytest + +from dissect.target.filesystems.ffs import FfsFilesystem, FfsFilesystemEntry + +NANOSECONDS_IN_SECOND = 1_000_000_000 + + +@pytest.fixture +def ffs_fs() -> Iterator[FfsFilesystem]: + with patch("dissect.ffs.ffs.FFS"): + ffs_fs = FfsFilesystem(Mock()) + ffs_fs.ffs.block_size = 32 * 1024 + yield ffs_fs + + +@pytest.fixture +def ffs_fs_entry(ffs_fs: FfsFilesystem) -> Iterator[FfsFilesystemEntry]: + atime = datetime(2024, 10, 1, 12, 0, 0) + mtime = datetime(2024, 10, 2, 12, 0, 0) + ctime = datetime(2024, 10, 3, 12, 0, 0) + btime = datetime(2024, 10, 4, 12, 0, 0) + + raw_inode = Mock(di_uid=1000, di_nlink=1, di_guid=999, di_size=165002) + inode = Mock( + mode=0o100664, + inum=4, + inode=raw_inode, + nblocks=323, + atime=atime, + atime_ns=atime.timestamp() * NANOSECONDS_IN_SECOND, + mtime=mtime, + mtime_ns=mtime.timestamp() * NANOSECONDS_IN_SECOND, + ctime=ctime, + ctime_ns=ctime.timestamp() * NANOSECONDS_IN_SECOND, + btime=btime, + btime_ns=btime.timestamp() * NANOSECONDS_IN_SECOND, + is_file=lambda: True, + is_dir=lambda: False, + is_symlink=lambda: False, + ) + + entry = FfsFilesystemEntry(ffs_fs, "/some_file", inode) + yield entry + + +def test_jffs2_stat(ffs_fs_entry: FfsFilesystemEntry) -> None: + stat = ffs_fs_entry.stat() + + entry = ffs_fs_entry.entry + assert stat.st_mode == entry.mode + assert stat.st_ino == entry.inum + assert stat.st_dev == id(ffs_fs_entry.fs) + assert stat.st_nlink == entry.inode.di_nlink + assert stat.st_uid == entry.inode.di_uid + assert stat.st_gid == entry.inode.di_gid + assert stat.st_size == entry.inode.di_size + assert stat.st_atime == entry.atime.timestamp() + assert stat.st_mtime == entry.mtime.timestamp() + assert stat.st_ctime == entry.ctime.timestamp() + assert stat.st_birthtime == entry.btime.timestamp() + assert stat.st_birthtime_ns == entry.btime_ns + assert stat.st_blksize == 32 * 1024 + assert stat.st_blocks == 323