"""The SleuthKit (TSK) file entry implementation."""
import copy
import decimal
from dfdatetime import definitions as dfdatetime_definitions
from dfdatetime import factory as dfdatetime_factory
from dfdatetime import interface as dfdatetime_interface
import pytsk3
from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import tsk_path_spec
from dfvfs.resolver import resolver
from dfvfs.vfs import attribute
from dfvfs.vfs import extent
from dfvfs.vfs import file_entry
from dfvfs.vfs import tsk_attribute
from dfvfs.vfs import tsk_data_stream
from dfvfs.vfs import tsk_directory
[docs]
class TSKTime(dfdatetime_interface.DateTimeValues):
"""SleuthKit timestamp.
Attributes:
fraction_of_second (int): fraction of second, which is an integer that
contains the number 100 nano seconds before Sleuthkit 4.2.0 or
number of nano seconds in Sleuthkit 4.2.0 and later.
"""
_100_NANOSECONDS_PER_SECOND = 10000000
_NANOSECONDS_PER_SECOND = 1000000000
[docs]
def __init__(
self,
fraction_of_second=None,
precision=None,
time_zone_offset=None,
timestamp=None,
):
"""Initializes a SleuthKit timestamp.
Args:
fraction_of_second (Optional[int]): fraction of second, which is
an integer that contains the number 100 nano seconds before
Sleuthkit 4.2.0 or number of nano seconds in Sleuthkit 4.2.0
and later.
precision (Optional[int]): precision of the date and time value, which
should be one of the PRECISION_VALUES in dfDateTime definitions.
time_zone_offset (Optional[int]): time zone offset in number of minutes
from UTC or None if not set.
timestamp (Optional[int]): POSIX timestamp.
"""
# Sleuthkit 4.2.0 switched from 100 nano seconds granularity to
# 1 nano second granularity.
if pytsk3.TSK_VERSION_NUM >= 0x040200FF:
granularity = dfdatetime_definitions.PRECISION_1_NANOSECOND
else:
granularity = dfdatetime_definitions.PRECISION_100_NANOSECONDS
super().__init__(
precision=precision or granularity, time_zone_offset=time_zone_offset
)
self._granularity = granularity
self._timestamp = timestamp
self.fraction_of_second = fraction_of_second
@property
def timestamp(self):
"""int: POSIX timestamp in microseconds or None if timestamp is not set."""
return self._timestamp
def _GetNormalizedTimestamp(self):
"""Retrieves the normalized timestamp.
Returns:
decimal.Decimal: normalized timestamp, which contains the number of
seconds since January 1, 1970 00:00:00 and a fraction of second used
for increased precision, or None if the normalized timestamp cannot be
determined.
"""
if self._normalized_timestamp is None:
if self._timestamp is not None:
self._normalized_timestamp = decimal.Decimal(self._timestamp)
if self.fraction_of_second is not None:
fraction_of_second = decimal.Decimal(self.fraction_of_second)
if (
self._granularity
== dfdatetime_definitions.PRECISION_1_NANOSECOND
):
fraction_of_second /= self._NANOSECONDS_PER_SECOND
else:
fraction_of_second /= self._100_NANOSECONDS_PER_SECOND
self._normalized_timestamp += fraction_of_second
if self._time_zone_offset:
self._normalized_timestamp -= self._time_zone_offset * 60
return self._normalized_timestamp
[docs]
def CopyFromDateTimeString(self, time_string):
"""Copies a SleuthKit timestamp from a date and time string.
Args:
time_string (str): date and time value formatted as:
YYYY-MM-DD hh:mm:ss.######[+-]##:##
Where # are numeric digits ranging from 0 to 9 and the seconds
fraction can be either 3, 6 or 9 digits. The time of day, seconds
fraction and time zone offset are optional. The default time zone
is UTC.
"""
date_time_values = self._CopyDateTimeFromString(time_string)
year = date_time_values.get("year", 0)
month = date_time_values.get("month", 0)
day_of_month = date_time_values.get("day_of_month", 0)
hours = date_time_values.get("hours", 0)
minutes = date_time_values.get("minutes", 0)
seconds = date_time_values.get("seconds", 0)
nanoseconds = date_time_values.get("nanoseconds")
time_zone_offset = date_time_values.get("time_zone_offset", 0)
self._timestamp = self._GetNumberOfSecondsFromElements(
year, month, day_of_month, hours, minutes, seconds
)
if nanoseconds is not None:
self.fraction_of_second = nanoseconds
else:
# TODO: kept for backwards compatibility with older dfdatetime versions.
self.fraction_of_second = date_time_values.get("microseconds", 0) * 1000
self._precision = dfdatetime_definitions.PRECISION_1_NANOSECOND
self._time_zone_offset = time_zone_offset
self._normalized_timestamp = None
self.is_local_time = False
[docs]
def CopyToDateTimeString(self):
"""Copies the date time value to a date and time string.
Returns:
str: date and time value formatted as:
YYYY-MM-DD hh:mm:ss or
YYYY-MM-DD hh:mm:ss.####### or
YYYY-MM-DD hh:mm:ss.#########
"""
if self._timestamp is None:
return None
number_of_days, hours, minutes, seconds = self._GetTimeValues(self._timestamp)
year, month, day_of_month = self._GetDateValues(number_of_days, 1970, 1, 1)
if self.fraction_of_second is None:
return (
f"{year:04d}-{month:02d}-{day_of_month:02d} "
f"{hours:02d}:{minutes:02d}:{seconds:02d}"
)
if self._precision == dfdatetime_definitions.PRECISION_1_NANOSECOND:
return (
f"{year:04d}-{month:02d}-{day_of_month:02d} "
f"{hours:02d}:{minutes:02d}:{seconds:02d}"
f".{self.fraction_of_second:09d}"
)
return (
f"{year:04d}-{month:02d}-{day_of_month:02d} "
f"{hours:02d}:{minutes:02d}:{seconds:02d}"
f".{self.fraction_of_second:07d}"
)
[docs]
def GetDate(self):
"""Retrieves the date represented by the date and time values.
Returns:
tuple[int, int, int]: year, month, day of month or (None, None, None)
if the date and time values do not represent a date.
"""
if self._timestamp is None:
return None, None, None
try:
number_of_days, _, _, _ = self._GetTimeValues(self._timestamp)
return self._GetDateValues(number_of_days, 1970, 1, 1)
except ValueError:
return None, None, None
[docs]
class TSKFileEntry(file_entry.FileEntry):
"""File system file entry that uses pytsk3."""
TYPE_INDICATOR = definitions.TYPE_INDICATOR_TSK
# pytsk3.TSK_FS_TYPE_ENUM is unhashable, preventing a set
# based lookup, hence lists are used.
_TSK_EXT_FS_TYPES = [
pytsk3.TSK_FS_TYPE_EXT2,
pytsk3.TSK_FS_TYPE_EXT3,
pytsk3.TSK_FS_TYPE_EXT4,
pytsk3.TSK_FS_TYPE_EXT_DETECT,
]
_TSK_FAT_FS_TYPES = [
pytsk3.TSK_FS_TYPE_EXFAT,
pytsk3.TSK_FS_TYPE_FAT_DETECT,
pytsk3.TSK_FS_TYPE_FAT12,
pytsk3.TSK_FS_TYPE_FAT16,
pytsk3.TSK_FS_TYPE_FAT32,
]
_TSK_HFS_FS_TYPES = [pytsk3.TSK_FS_TYPE_HFS, pytsk3.TSK_FS_TYPE_HFS_DETECT]
_TSK_ISO9660_FS_TYPES = [
pytsk3.TSK_FS_TYPE_ISO9660,
pytsk3.TSK_FS_TYPE_ISO9660_DETECT,
]
_TSK_NTFS_FS_TYPES = [pytsk3.TSK_FS_TYPE_NTFS, pytsk3.TSK_FS_TYPE_NTFS_DETECT]
_TSK_UFS_FS_TYPES = [
pytsk3.TSK_FS_TYPE_FFS_DETECT,
pytsk3.TSK_FS_TYPE_FFS1,
pytsk3.TSK_FS_TYPE_FFS1B,
pytsk3.TSK_FS_TYPE_FFS2,
]
_TSK_ATIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_YAFFS2]
_TSK_ATIME_FS_TYPES.extend(_TSK_EXT_FS_TYPES)
_TSK_ATIME_FS_TYPES.extend(_TSK_FAT_FS_TYPES)
_TSK_ATIME_FS_TYPES.extend(_TSK_HFS_FS_TYPES)
_TSK_ATIME_FS_TYPES.extend(_TSK_NTFS_FS_TYPES)
_TSK_ATIME_FS_TYPES.extend(_TSK_UFS_FS_TYPES)
_TSK_BKUP_FS_TYPES = _TSK_HFS_FS_TYPES
_TSK_CTIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_YAFFS2]
_TSK_CTIME_FS_TYPES.extend(_TSK_EXT_FS_TYPES)
_TSK_CTIME_FS_TYPES.extend(_TSK_HFS_FS_TYPES)
_TSK_CTIME_FS_TYPES.extend(_TSK_NTFS_FS_TYPES)
_TSK_CTIME_FS_TYPES.extend(_TSK_UFS_FS_TYPES)
_TSK_CRTIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_EXT4]
_TSK_CRTIME_FS_TYPES.extend(_TSK_FAT_FS_TYPES)
_TSK_CRTIME_FS_TYPES.extend(_TSK_HFS_FS_TYPES)
_TSK_CRTIME_FS_TYPES.extend(_TSK_ISO9660_FS_TYPES)
_TSK_CRTIME_FS_TYPES.extend(_TSK_NTFS_FS_TYPES)
_TSK_DTIME_FS_TYPES = _TSK_EXT_FS_TYPES
_TSK_MTIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_YAFFS2]
_TSK_MTIME_FS_TYPES.extend(_TSK_EXT_FS_TYPES)
_TSK_MTIME_FS_TYPES.extend(_TSK_FAT_FS_TYPES)
_TSK_MTIME_FS_TYPES.extend(_TSK_HFS_FS_TYPES)
_TSK_MTIME_FS_TYPES.extend(_TSK_NTFS_FS_TYPES)
_TSK_MTIME_FS_TYPES.extend(_TSK_UFS_FS_TYPES)
_TSK_HAS_NANO_FS_TYPES = frozenset(
[
pytsk3.TSK_FS_TYPE_EXFAT,
pytsk3.TSK_FS_TYPE_EXT4,
pytsk3.TSK_FS_TYPE_FFS2,
pytsk3.TSK_FS_TYPE_HFS,
pytsk3.TSK_FS_TYPE_NTFS,
]
)
_TSK_INTERNAL_ATTRIBUTE_TYPES = frozenset(
[
pytsk3.TSK_FS_ATTR_TYPE_APFS_COMP_REC,
pytsk3.TSK_FS_ATTR_TYPE_APFS_DATA,
pytsk3.TSK_FS_ATTR_TYPE_APFS_RSRC,
pytsk3.TSK_FS_ATTR_TYPE_HFS_COMP_REC,
pytsk3.TSK_FS_ATTR_TYPE_HFS_DATA,
pytsk3.TSK_FS_ATTR_TYPE_HFS_RSRC,
pytsk3.TSK_FS_ATTR_TYPE_UNIX_EXTENT,
pytsk3.TSK_FS_ATTR_TYPE_UNIX_INDIR,
]
)
_ATTRIBUTE_TYPE_CLASS_MAPPINGS = {
pytsk3.TSK_FS_ATTR_TYPE_HFS_EXT_ATTR: tsk_attribute.TSKExtendedAttribute
}
[docs]
def __init__(
self,
resolver_context,
file_system,
path_spec,
is_root=False,
is_virtual=False,
parent_inode=None,
tsk_file=None,
):
"""Initializes a file entry.
Args:
resolver_context (Context): resolver context.
file_system (TSKFileSystem): file system.
path_spec (PathSpec): path specification.
is_root (Optional[bool]): True if the file entry is the root file entry
of the corresponding file system.
is_virtual (Optional[bool]): True if the file entry is a virtual file
entry emulated by the corresponding file system.
parent_inode (Optional[int]): parent inode number.
tsk_file (Optional[pytsk3.File]): TSK file.
Raises:
BackEndError: if the TSK File .info or .info.meta attribute is missing.
"""
if (
not tsk_file
or not tsk_file.info
or not tsk_file.info.meta
or not tsk_file.info.fs_info
):
tsk_file = file_system.GetTSKFileByPathSpec(path_spec)
if (
not tsk_file
or not tsk_file.info
or not tsk_file.info.meta
or not tsk_file.info.fs_info
):
raise errors.BackEndError(
"Missing TSK File .info, .info.meta or .info.fs_info"
)
super().__init__(
resolver_context,
file_system,
path_spec,
is_root=is_root,
is_virtual=is_virtual,
)
self._file_system_type = tsk_file.info.fs_info.ftype
self._name = None
self._parent_inode = parent_inode
self._tsk_file = tsk_file
# The type is an instance of pytsk3.TSK_FS_META_TYPE_ENUM.
tsk_fs_meta_type = getattr(
tsk_file.info.meta, "type", pytsk3.TSK_FS_META_TYPE_UNDEF
)
if tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_REG:
self.entry_type = definitions.FILE_ENTRY_TYPE_FILE
elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_DIR:
self.entry_type = definitions.FILE_ENTRY_TYPE_DIRECTORY
elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_LNK:
self.entry_type = definitions.FILE_ENTRY_TYPE_LINK
elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_CHR:
self.entry_type = definitions.FILE_ENTRY_TYPE_CHARACTER_DEVICE
elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_BLK:
self.entry_type = definitions.FILE_ENTRY_TYPE_BLOCK_DEVICE
elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_FIFO:
self.entry_type = definitions.FILE_ENTRY_TYPE_PIPE
elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_SOCK:
self.entry_type = definitions.FILE_ENTRY_TYPE_SOCKET
# TODO: implement support for:
# pytsk3.TSK_FS_META_TYPE_UNDEF
# pytsk3.TSK_FS_META_TYPE_SHAD
# pytsk3.TSK_FS_META_TYPE_WHT
# pytsk3.TSK_FS_META_TYPE_VIRT
def _GetAttributes(self):
"""Retrieves the attributes.
Returns:
list[TSKAttribute]: attributes.
"""
if self._attributes is None:
self._attributes = []
for pytsk_attribute in self._tsk_file:
if getattr(pytsk_attribute, "info", None):
attribute_type = getattr(pytsk_attribute.info, "type", None)
if attribute_type in self._TSK_INTERNAL_ATTRIBUTE_TYPES:
continue
# The data stream is returned as a name-less attribute of type
# pytsk3.TSK_FS_ATTR_TYPE_DEFAULT.
if (
attribute_type == pytsk3.TSK_FS_ATTR_TYPE_DEFAULT
and not getattr(pytsk_attribute.info, "name", None)
):
continue
if self._file_system.IsExt():
attribute_class = tsk_attribute.TSKExtendedAttribute
else:
attribute_class = self._ATTRIBUTE_TYPE_CLASS_MAPPINGS.get(
attribute_type, tsk_attribute.TSKAttribute
)
attribute_object = attribute_class(self._tsk_file, pytsk_attribute)
self._attributes.append(attribute_object)
return self._attributes
def _GetDataStreams(self):
"""Retrieves the data streams.
Returns:
list[TSKDataStream]: data streams.
"""
if self._data_streams is None:
if self._file_system.IsHFS():
known_data_attribute_types = (
pytsk3.TSK_FS_ATTR_TYPE_HFS_DEFAULT,
pytsk3.TSK_FS_ATTR_TYPE_HFS_DATA,
pytsk3.TSK_FS_ATTR_TYPE_HFS_RSRC,
)
elif self._file_system.IsNTFS():
known_data_attribute_types = (pytsk3.TSK_FS_ATTR_TYPE_NTFS_DATA,)
else:
known_data_attribute_types = None
self._data_streams = []
tsk_fs_meta_type = getattr(
self._tsk_file.info.meta, "type", pytsk3.TSK_FS_META_TYPE_UNDEF
)
if not known_data_attribute_types:
if tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_REG:
data_stream = tsk_data_stream.TSKDataStream(self, None)
self._data_streams.append(data_stream)
else:
for pytsk_attribute in self._tsk_file:
# NTFS allows directories to have data streams.
if (
not self._file_system.IsNTFS()
and tsk_fs_meta_type != pytsk3.TSK_FS_META_TYPE_REG
):
continue
if getattr(pytsk_attribute, "info", None) is None:
continue
attribute_type = getattr(pytsk_attribute.info, "type", None)
if attribute_type in known_data_attribute_types:
data_stream = tsk_data_stream.TSKDataStream(
self, pytsk_attribute
)
self._data_streams.append(data_stream)
return self._data_streams
def _GetDirectory(self):
"""Retrieves a directory.
Returns:
TSKDirectory: a directory.
"""
if self.entry_type != definitions.FILE_ENTRY_TYPE_DIRECTORY:
return None
return tsk_directory.TSKDirectory(self._file_system, self.path_spec)
def _GetLink(self):
"""Retrieves the link.
Returns:
str: path of the linked file.
Raises:
BackEndError: if the link is not formatted in UTF-8.
"""
if self._link is None:
self._link = ""
# Note that the SleuthKit does not expose NTFS
# IO_REPARSE_TAG_MOUNT_POINT or IO_REPARSE_TAG_SYMLINK as a link.
link = getattr(self._tsk_file.info.meta, "link", None)
if link is None:
return self._link
try:
# pytsk3 returns an UTF-8 encoded byte string without a leading
# path segment separator.
link = link.decode("utf8")
except UnicodeError:
raise errors.BackEndError("pytsk3 returned a non UTF-8 formatted link.")
self._link = "".join([self._file_system.PATH_SEPARATOR, link])
return self._link
def _GetStatAttribute(self):
"""Retrieves a stat attribute.
Returns:
StatAttribute: a stat attribute or None if not available.
"""
mode = getattr(self._tsk_file.info.meta, "mode", None)
if mode is not None:
# We need to cast mode to an int since it is of type
# pytsk3.TSK_FS_META_MODE_ENUM.
mode = int(mode)
stat_attribute = attribute.StatAttribute()
stat_attribute.group_identifier = getattr(self._tsk_file.info.meta, "gid", None)
stat_attribute.inode_number = getattr(self._tsk_file.info.meta, "addr", None)
stat_attribute.mode = mode
stat_attribute.number_of_links = getattr(
self._tsk_file.info.meta, "nlink", None
)
stat_attribute.owner_identifier = getattr(self._tsk_file.info.meta, "uid", None)
stat_attribute.size = getattr(self._tsk_file.info.meta, "size", None)
stat_attribute.type = self.entry_type
return stat_attribute
def _GetSubFileEntries(self):
"""Retrieves sub file entries.
Yields:
TSKFileEntry: a sub file entry.
"""
if self._directory is None:
self._directory = self._GetDirectory()
if self._directory:
for path_spec in self._directory.entries:
yield TSKFileEntry(self._resolver_context, self._file_system, path_spec)
def _GetTimeValue(self, name):
"""Retrieves a date and time value.
Args:
name (str): name of the date and time value, for example "atime" or
"mtime".
Returns:
dfdatetime.DateTimeValues: date and time value or None if not available.
"""
timestamp = getattr(self._tsk_file.info.meta, name, None)
if timestamp is None:
return None
if self._file_system_type in self._TSK_HAS_NANO_FS_TYPES:
fraction_of_second = getattr(
self._tsk_file.info.meta, f"{name:s}_nano", None
)
else:
fraction_of_second = None
is_local_time = False
precision = None
if self._file_system_type in (pytsk3.TSK_FS_TYPE_EXT2, pytsk3.TSK_FS_TYPE_EXT3):
precision = dfdatetime_definitions.PRECISION_1_SECOND
elif self._file_system_type == pytsk3.TSK_FS_TYPE_EXT4:
# Note that pytsk3 can return 0 for an ext4 creation time even if the
# inode does not contain it.
if name == "crtime" and timestamp == 0:
return None
elif self._file_system_type in (
pytsk3.TSK_FS_TYPE_FAT12,
pytsk3.TSK_FS_TYPE_FAT16,
pytsk3.TSK_FS_TYPE_FAT32,
):
is_local_time = True
if name == "atime":
precision = dfdatetime_definitions.PRECISION_1_DAY
else:
precision = dfdatetime_definitions.PRECISION_2_SECONDS
elif self._file_system_type == pytsk3.TSK_FS_TYPE_NTFS:
precision = dfdatetime_definitions.PRECISION_100_NANOSECONDS
# TODO: determine if file system type is traditional HFS and set
# is_local_time accordingly.
date_time = TSKTime(
fraction_of_second=fraction_of_second,
precision=precision,
timestamp=timestamp,
)
date_time.is_local_time = is_local_time
return date_time
@property
def access_time(self):
"""dfdatetime.DateTimeValues: access time or None if not available."""
if self._file_system_type not in self._TSK_ATIME_FS_TYPES:
return None
return self._GetTimeValue("atime")
# TODO: add added time support, at least provided for APFS.
@property
def backup_time(self):
"""dfdatetime.DateTimeValues: backup time or None if not available."""
if self._file_system_type not in self._TSK_BKUP_FS_TYPES:
return None
return self._GetTimeValue("bkup")
@property
def change_time(self):
"""dfdatetime.DateTimeValues: change time or None if not available."""
if self._file_system_type not in self._TSK_CTIME_FS_TYPES:
return None
return self._GetTimeValue("ctime")
@property
def creation_time(self):
"""dfdatetime.DateTimeValues: creation time or None if not available."""
if self._file_system_type not in self._TSK_CRTIME_FS_TYPES:
return None
return self._GetTimeValue("crtime")
@property
def deletion_time(self):
"""dfdatetime.DateTimeValues: deletion time or None if not available."""
if self._file_system_type not in self._TSK_DTIME_FS_TYPES:
return None
return self._GetTimeValue("dtime")
@property
def modification_time(self):
"""dfdatetime.DateTimeValues: modification time or None if not available."""
if self._file_system_type not in self._TSK_MTIME_FS_TYPES:
return None
return self._GetTimeValue("mtime")
# pylint: disable=missing-return-doc,missing-return-type-doc
@property
def name(self):
"""str: name of the file entry, which does not include the full path.
Raises:
BackEndError: if pytsk3 returns a non UTF-8 formatted name.
"""
if self._name is None:
# If pytsk3.FS_Info.open() was used file.info has an attribute name
# (pytsk3.TSK_FS_FILE) that contains the name string. Otherwise the
# name from the path specification is used.
if getattr(self._tsk_file.info, "name", None) is not None:
name = getattr(self._tsk_file.info.name, "name", None)
try:
# pytsk3 returns an UTF-8 encoded byte string.
self._name = name.decode("utf8")
except UnicodeError:
raise errors.BackEndError(
"pytsk3 returned a non UTF-8 formatted name."
)
else:
location = getattr(self.path_spec, "location", None)
if location:
self._name = self._file_system.BasenamePath(location)
return self._name
@property
def size(self):
"""int: size of the file entry in bytes or None if not available."""
return getattr(self._tsk_file.info.meta, "size", None)
[docs]
def GetExtents(self):
"""Retrieves the extents.
Returns:
list[Extent]: the extents.
Raises:
BackEndError: if pytsk3 returns no file system block size or data stream
size.
"""
if self.entry_type != definitions.FILE_ENTRY_TYPE_FILE:
return []
data_attribute = None
for pytsk_attribute in self._tsk_file:
if not getattr(pytsk_attribute, "info", None):
continue
attribute_name = getattr(pytsk_attribute.info, "name", None)
attribute_type = getattr(pytsk_attribute.info, "type", None)
# The data stream is returned as a name-less attribute of type
# pytsk3.TSK_FS_ATTR_TYPE_DEFAULT, pytsk3.TSK_FS_ATTR_TYPE_NTFS_DATA or
# pytsk3.TSK_FS_ATTR_TYPE_NTFS_DATA
if not attribute_name and attribute_type in (
pytsk3.TSK_FS_ATTR_TYPE_DEFAULT,
pytsk3.TSK_FS_ATTR_TYPE_HFS_DATA,
pytsk3.TSK_FS_ATTR_TYPE_NTFS_DATA,
):
data_attribute = pytsk_attribute
break
extents = []
if data_attribute:
tsk_file_system = self._file_system.GetFsInfo()
block_size = getattr(tsk_file_system.info, "block_size", None)
if not block_size:
raise errors.BackEndError("pytsk3 returned no file system block size.")
data_stream_size = getattr(data_attribute.info, "size", None)
if data_stream_size is None:
raise errors.BackEndError("pytsk3 returned no data stream size.")
data_stream_number_of_blocks, remainder = divmod(
data_stream_size, block_size
)
if remainder:
data_stream_number_of_blocks += 1
total_number_of_blocks = 0
for tsk_attr_run in data_attribute:
if tsk_attr_run.flags & pytsk3.TSK_FS_ATTR_RUN_FLAG_SPARSE:
extent_type = definitions.EXTENT_TYPE_SPARSE
else:
extent_type = definitions.EXTENT_TYPE_DATA
extent_offset = tsk_attr_run.addr * block_size
extent_size = tsk_attr_run.len
# Note that the attribute data runs can be larger than the actual
# allocated size.
if total_number_of_blocks + extent_size > data_stream_number_of_blocks:
extent_size = data_stream_number_of_blocks - total_number_of_blocks
total_number_of_blocks += extent_size
extent_size *= block_size
data_stream_extent = extent.Extent(
extent_type=extent_type, offset=extent_offset, size=extent_size
)
extents.append(data_stream_extent)
if total_number_of_blocks >= data_stream_number_of_blocks:
break
return extents
[docs]
def GetFileObject(self, data_stream_name=""):
"""Retrieves a file-like object of a specific data stream.
Args:
data_stream_name (Optional[str]): data stream name, where an empty
string represents the default data stream.
Returns:
TSKFileIO: file-like object or None.
"""
data_stream_names = [data_stream.name for data_stream in self._GetDataStreams()]
if data_stream_name and data_stream_name not in data_stream_names:
return None
path_spec = copy.deepcopy(self.path_spec)
if data_stream_name:
# For HFS DECOMP fork name is exposed however libtsk 4.6.0 seems to handle
# these differently when opened and the correct behavior seems to be
# treating this as the default (nameless) fork instead. For context libtsk
# 4.5.0 is unable to read the data steam and yields an error.
if not self._file_system.IsHFS() or data_stream_name != "DECOMP":
setattr(path_spec, "data_stream", data_stream_name)
return resolver.Resolver.OpenFileObject(
path_spec, resolver_context=self._resolver_context
)
[docs]
def GetLinkedFileEntry(self):
"""Retrieves the linked file entry, e.g. for a symbolic link.
Returns:
TSKFileEntry: linked file entry or None.
"""
link = self._GetLink()
if not link:
return None
# TODO: is there a way to determine the link inode number here?
link_inode = None
parent_path_spec = getattr(self.path_spec, "parent", None)
path_spec = tsk_path_spec.TSKPathSpec(location=link, parent=parent_path_spec)
root_inode = self._file_system.GetRootInode()
is_root = bool(
link == self._file_system.LOCATION_ROOT
or (
link_inode is not None
and root_inode is not None
and link_inode == root_inode
)
)
return TSKFileEntry(
self._resolver_context, self._file_system, path_spec, is_root=is_root
)
[docs]
def GetParentFileEntry(self):
"""Retrieves the parent file entry.
Returns:
TSKFileEntry: parent file entry or None.
"""
location = getattr(self.path_spec, "location", None)
if location is None:
return None
parent_inode = self._parent_inode
parent_location = self._file_system.DirnamePath(location)
if parent_inode is None and parent_location is None:
return None
if parent_location == "":
parent_location = self._file_system.PATH_SEPARATOR
root_inode = self._file_system.GetRootInode()
is_root = bool(
parent_location == self._file_system.LOCATION_ROOT
or (
parent_inode is not None
and root_inode is not None
and parent_inode == root_inode
)
)
parent_path_spec = getattr(self.path_spec, "parent", None)
path_spec = tsk_path_spec.TSKPathSpec(
inode=parent_inode, location=parent_location, parent=parent_path_spec
)
return TSKFileEntry(
self._resolver_context, self._file_system, path_spec, is_root=is_root
)
[docs]
def GetTSKFile(self):
"""Retrieves the SleuthKit file object.
Returns:
pytsk3.File: TSK file.
Raises:
PathSpecError: if the path specification is missing inode and location.
"""
return self._tsk_file
[docs]
def IsAllocated(self):
"""Determines if the file entry is allocated.
Returns:
bool: True if the file entry is allocated.
"""
# The flags are an instance of pytsk3.TSK_FS_META_FLAG_ENUM.
flags = getattr(self._tsk_file.info.meta, "flags", 0)
return bool(int(flags) & pytsk3.TSK_FS_META_FLAG_ALLOC)
dfdatetime_factory.Factory.RegisterDateTimeValues(TSKTime)