"""The SleuthKit (TSK) directory implementation."""
import pytsk3
from dfvfs.lib import errors
from dfvfs.path import tsk_path_spec
from dfvfs.vfs import directory
[docs]
class TSKDirectory(directory.Directory):
"""File system directory that uses pytsk3."""
def _EntriesGenerator(self):
"""Retrieves directory entries.
Since a directory can contain a vast number of entries using
a generator is more memory efficient.
Yields:
TSKPathSpec: a path specification.
Raises:
BackEndError: if pytsk3 cannot open the directory.
"""
# Opening a file by inode number is faster than opening a file
# by location.
inode = getattr(self.path_spec, "inode", None)
location = getattr(self.path_spec, "location", None)
fs_info = self._file_system.GetFsInfo()
tsk_directory = None
try:
if inode is not None:
tsk_directory = fs_info.open_dir(inode=inode)
elif location is not None:
tsk_directory = fs_info.open_dir(path=location)
except OSError as exception:
raise errors.BackEndError(
f"Unable to open directory with error: {exception!s}"
)
if tsk_directory:
for tsk_directory_entry in tsk_directory:
# Note that because pytsk3.Directory does not explicitly define info
# we need to check if the attribute exists and has a value other
# than None.
if getattr(tsk_directory_entry, "info", None) is None:
continue
# Note that because pytsk3.TSK_FS_FILE does not explicitly define
# fs_info we need to check if the attribute exists and has a value
# other than None.
if getattr(tsk_directory_entry.info, "fs_info", None) is None:
continue
# Note that because pytsk3.TSK_FS_FILE does not explicitly define meta
# we need to check if the attribute exists and has a value other
# than None.
if getattr(tsk_directory_entry.info, "meta", None) is None:
# Most directory entries will have an "inode" but not all, e.g.
# previously deleted files. Currently directory entries without
# a pytsk3.TSK_FS_META object are ignored.
continue
# Note that because pytsk3.TSK_FS_META does not explicitly define addr
# we need to check if the attribute exists.
if not hasattr(tsk_directory_entry.info.meta, "addr"):
continue
directory_entry_inode = tsk_directory_entry.info.meta.addr
directory_entry = None
# Ignore references to self.
if directory_entry_inode == inode:
continue
# On non-NTFS file systems ignore inode 0.
if directory_entry_inode == 0 and not self._file_system.IsNTFS():
continue
# Note that because pytsk3.TSK_FS_FILE does not explicitly define name
# we need to check if the attribute exists and has a value other
# than None.
if getattr(tsk_directory_entry.info, "name", None) is not None:
# Ignore file entries marked as "unallocated".
flags = getattr(tsk_directory_entry.info.name, "flags", 0)
if int(flags) & pytsk3.TSK_FS_NAME_FLAG_UNALLOC:
continue
directory_entry = getattr(tsk_directory_entry.info.name, "name", "")
try:
# pytsk3 returns an UTF-8 encoded byte string.
directory_entry = directory_entry.decode("utf8")
except UnicodeError:
# Continue here since we cannot represent the directory entry.
continue
if directory_entry:
# Ignore references to self or parent.
if directory_entry in [".", ".."]:
continue
if not location or location == self._file_system.PATH_SEPARATOR:
directory_entry = self._file_system.JoinPath(
[directory_entry]
)
else:
directory_entry = self._file_system.JoinPath(
[location, directory_entry]
)
yield tsk_path_spec.TSKPathSpec(
inode=directory_entry_inode,
location=directory_entry,
parent=self.path_spec.parent,
)