Skip to content

Commit

Permalink
Add XFS metadata: number of blocks per file. (#874)
Browse files Browse the repository at this point in the history
- Add the number of blocks to the stat output.
- Add the birthtime in nanoseconds to the stat output
  • Loading branch information
twiggler authored Oct 10, 2024
1 parent 45bacf3 commit 2389376
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 2 deletions.
9 changes: 8 additions & 1 deletion dissect/target/filesystems/xfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,15 @@ def lstat(self) -> fsutil.stat_result:
st_info.st_mtime_ns = self.entry.mtime_ns
st_info.st_ctime_ns = self.entry.ctime_ns

# XFS has a birth time, called crtime
st_info.st_blksize = self.fs.xfs.block_size
# Convert number of filesystem blocks to basic blocks
# Reference: https://github.com/torvalds/linux/blob/e32cde8d2bd7d251a8f9b434143977ddf13dcec6/fs/xfs/xfs_iops.c#L602 # noqa: E501
# Note that block size in XFS is always a multiple of 512, so the division below is safe
st_info.st_blocks = self.entry.nblocks * (self.fs.xfs.block_size // 512)

# XFS has a birth time, since inode version 3 (version 5 of filesystem)
st_info.st_birthtime = self.entry.crtime.timestamp()
st_info.st_birthtime_ns = self.entry.crtime_ns

return st_info

Expand Down
2 changes: 1 addition & 1 deletion dissect/target/tools/fsutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def filetype(path: TargetPath) -> str:
filetype=filetype(path),
device="?",
inode=s.st_ino,
blocks=s.st_blocks or "?",
blocks=s.st_blocks if s.st_blocks is not None else "?",
blksize=s.st_blksize or "?",
nlink=s.st_nlink,
modeord=oct(stat.S_IMODE(s.st_mode)),
Expand Down
67 changes: 67 additions & 0 deletions tests/filesystems/test_xfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from datetime import datetime
from typing import Iterator
from unittest.mock import Mock, patch

import pytest

from dissect.target.filesystems.xfs import XfsFilesystem, XfsFilesystemEntry

NANOSECONDS_IN_SECOND = 1_000_000_000


@pytest.fixture
def xfs_fs() -> Iterator[XfsFilesystem]:
with patch("dissect.xfs.xfs.XFS"):
xfs_fs = XfsFilesystem(Mock())
xfs_fs.xfs.block_size = 4096

yield xfs_fs


@pytest.fixture
def xfs_fs_entry(xfs_fs: XfsFilesystem) -> Iterator[XfsFilesystemEntry]:
atime = datetime(2024, 10, 1, 12, 0, 0)
mtime = datetime(2024, 10, 2, 12, 0, 0)
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)
inode = Mock(
nblocks=10,
inode=dinode,
inum=4,
size=4594,
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,
crtime=crtime,
crtime_ns=crtime.timestamp() * NANOSECONDS_IN_SECOND,
)
entry = XfsFilesystemEntry(xfs_fs, "/some_file", inode)
yield entry


def test_xfs_stat(xfs_fs: XfsFilesystem, xfs_fs_entry: XfsFilesystemEntry) -> None:
stat = xfs_fs_entry.stat()

entry = xfs_fs_entry.entry
assert stat.st_mode == entry.inode.di_mode
assert stat.st_ino == entry.inum
assert stat.st_dev == id(xfs_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.size
assert stat.st_atime == entry.atime.timestamp()
assert stat.st_atime_ns == entry.atime_ns
assert stat.st_mtime == entry.mtime.timestamp()
assert stat.st_mtime_ns == entry.mtime_ns
assert stat.st_ctime == entry.ctime.timestamp()
assert stat.st_ctime_ns == entry.ctime_ns
assert stat.st_birthtime == entry.crtime.timestamp()
assert stat.st_birthtime_ns == entry.crtime_ns
assert stat.st_blksize == 4096
assert stat.st_blocks == 10 * 4096 // 512

0 comments on commit 2389376

Please sign in to comment.