Skip to content

Commit

Permalink
Added fsfat back-end log2timeline#580
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimmetz committed Jul 29, 2022
1 parent 55e01ab commit 247a0f9
Show file tree
Hide file tree
Showing 22 changed files with 1,471 additions and 8 deletions.
10 changes: 9 additions & 1 deletion dependencies.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ version_property: __version__

[dfdatetime]
dpkg_name: python3-dfdatetime
minimum_version: 20211113
minimum_version: 20220728
rpm_name: python3-dfdatetime
version_property: __version__

Expand Down Expand Up @@ -63,6 +63,14 @@ pypi_name: libfsext-python
rpm_name: libfsext-python3
version_property: get_version()

[pyfsfat]
dpkg_name: libfsfat-python3
l2tbinaries_name: libfsfat
minimum_version: 20220729
pypi_name: libfsfat-python
rpm_name: libfsfat-python3
version_property: get_version()

[pyfshfs]
dpkg_name: libfshfs-python3
l2tbinaries_name: libfshfs
Expand Down
1 change: 1 addition & 0 deletions dfvfs/analyzer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dfvfs.analyzer import cs_analyzer_helper
from dfvfs.analyzer import ewf_analyzer_helper
from dfvfs.analyzer import ext_analyzer_helper
from dfvfs.analyzer import fat_analyzer_helper
from dfvfs.analyzer import gpt_analyzer_helper
from dfvfs.analyzer import gzip_analyzer_helper
from dfvfs.analyzer import hfs_analyzer_helper
Expand Down
52 changes: 52 additions & 0 deletions dfvfs/analyzer/fat_analyzer_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""The FAT format analyzer helper implementation."""

from dfvfs.analyzer import analyzer
from dfvfs.analyzer import analyzer_helper
from dfvfs.analyzer import specification
from dfvfs.lib import definitions


class FATAnalyzerHelper(analyzer_helper.AnalyzerHelper):
"""FAT analyzer helper."""

FORMAT_CATEGORIES = frozenset([
definitions.FORMAT_CATEGORY_FILE_SYSTEM])

TYPE_INDICATOR = definitions.TYPE_INDICATOR_FAT

def GetFormatSpecification(self):
"""Retrieves the format specification.
Returns:
FormatSpecification: format specification or None if the format cannot
be defined by a specification object.
"""
format_specification = specification.FormatSpecification(
self.type_indicator)

# Boot sector signature.
format_specification.AddNewSignature(b'\x55\xaa', offset=510)

# FAT-12 and FAT-16 file system hint.
format_specification.AddNewSignature(b'FAT12 ', offset=54)
format_specification.AddNewSignature(b'FAT16 ', offset=54)

# FAT-32 file system hint.
format_specification.AddNewSignature(b'FAT32 ', offset=82)

# exFAT file system signature.
format_specification.AddNewSignature(b'EXFAT ', offset=3)

return format_specification

def IsEnabled(self):
"""Determines if the analyzer helper is enabled.
Returns:
bool: True if the analyzer helper is enabled.
"""
return definitions.PREFERRED_FAT_BACK_END == self.TYPE_INDICATOR


analyzer.Analyzer.RegisterHelper(FATAnalyzerHelper())
21 changes: 16 additions & 5 deletions dfvfs/analyzer/tsk_analyzer_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,28 @@ def GetFormatSpecification(self):
format_specification = specification.FormatSpecification(
self.type_indicator)

# FAT volume header signature.
format_specification.AddNewSignature(b'\x55\xaa', offset=510)
if definitions.PREFERRED_FAT_BACK_END == self.TYPE_INDICATOR:
# Boot sector signature.
format_specification.AddNewSignature(b'\x55\xaa', offset=510)

# FAT-12 and FAT-16 file system hint.
format_specification.AddNewSignature(b'FAT12 ', offset=54)
format_specification.AddNewSignature(b'FAT16 ', offset=54)

# FAT-32 file system hint.
format_specification.AddNewSignature(b'FAT32 ', offset=82)

# exFAT file system signature.
format_specification.AddNewSignature(b'EXFAT ', offset=3)

if definitions.PREFERRED_NTFS_BACK_END == self.TYPE_INDICATOR:
# NTFS file system signature.
format_specification.AddNewSignature(b'NTFS ', offset=3)

# HFS boot block signature.
format_specification.AddNewSignature(b'LK', offset=0)

if definitions.PREFERRED_HFS_BACK_END == self.TYPE_INDICATOR:
# HFS boot block signature.
# format_specification.AddNewSignature(b'LK', offset=0)

# HFS+ file system signature.
format_specification.AddNewSignature(b'H+', offset=1024)

Expand Down
149 changes: 149 additions & 0 deletions dfvfs/file_io/fat_file_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
"""The Hierarchical File System (FAT) file-like object implementation."""

import os

from dfvfs.file_io import file_io
from dfvfs.resolver import resolver


class FATFile(file_io.FileIO):
"""File input/output (IO) object using pyfsfat.file_entry"""

def __init__(self, resolver_context, path_spec):
"""Initializes a file input/output (IO) object.
Args:
resolver_context (Context): resolver context.
path_spec (PathSpec): a path specification.
"""
super(FATFile, self).__init__(resolver_context, path_spec)
self._file_system = None
self._fsfat_data_stream = None
self._fsfat_file_entry = None

def _Close(self):
"""Closes the file-like object."""
self._fsfat_data_stream = None
self._fsfat_file_entry = None

self._file_system = None

def _Open(self, mode='rb'):
"""Opens the file-like object defined by path specification.
Args:
mode (Optional[str]): file access mode.
Raises:
AccessError: if the access to open the file was denied.
IOError: if the file-like object could not be opened.
NotSupported: if a data stream, like the resource or named fork, is
requested to be opened.
OSError: if the file-like object could not be opened.
PathSpecError: if the path specification is incorrect.
"""
data_stream_name = getattr(self._path_spec, 'data_stream', None)

self._file_system = resolver.Resolver.OpenFileSystem(
self._path_spec, resolver_context=self._resolver_context)

file_entry = self._file_system.GetFileEntryByPathSpec(self._path_spec)
if not file_entry:
raise IOError('Unable to open file entry.')

fsfat_data_stream = None
fsfat_file_entry = file_entry.GetFATFileEntry()
if not fsfat_file_entry:
raise IOError('Unable to open FAT file entry.')

if data_stream_name == 'rsrc':
fsfat_data_stream = fsfat_file_entry.get_resource_fork()
elif data_stream_name:
raise IOError('Unable to open data stream: {0:s}.'.format(
data_stream_name))

self._fsfat_data_stream = fsfat_data_stream
self._fsfat_file_entry = fsfat_file_entry

# Note: that the following functions do not follow the style guide
# because they are part of the file-like object interface.
# pylint: disable=invalid-name

def read(self, size=None):
"""Reads a byte string from the file-like object at the current offset.
The function will read a byte string of the specified size or
all of the remaining data if no size was specified.
Args:
size (Optional[int]): number of bytes to read, where None is all
remaining data.
Returns:
bytes: data read.
Raises:
IOError: if the read failed.
OSError: if the read failed.
"""
if not self._is_open:
raise IOError('Not opened.')

if self._fsfat_data_stream:
return self._fsfat_data_stream.read(size=size)
return self._fsfat_file_entry.read(size=size)

def seek(self, offset, whence=os.SEEK_SET):
"""Seeks to an offset within the file-like object.
Args:
offset (int): offset to seek to.
whence (Optional(int)): value that indicates whether offset is an absolute
or relative position within the file.
Raises:
IOError: if the seek failed.
OSError: if the seek failed.
"""
if not self._is_open:
raise IOError('Not opened.')

if self._fsfat_data_stream:
self._fsfat_data_stream.seek(offset, whence)
else:
self._fsfat_file_entry.seek(offset, whence)

def get_offset(self):
"""Retrieves the current offset into the file-like object.
Return:
int: current offset into the file-like object.
Raises:
IOError: if the file-like object has not been opened.
OSError: if the file-like object has not been opened.
"""
if not self._is_open:
raise IOError('Not opened.')

if self._fsfat_data_stream:
return self._fsfat_data_stream.get_offset()
return self._fsfat_file_entry.get_offset()

def get_size(self):
"""Retrieves the size of the file-like object.
Returns:
int: size of the file-like object data.
Raises:
IOError: if the file-like object has not been opened.
OSError: if the file-like object has not been opened.
"""
if not self._is_open:
raise IOError('Not opened.')

if self._fsfat_data_stream:
return self._fsfat_data_stream.get_size()
return self._fsfat_file_entry.get_size()
3 changes: 3 additions & 0 deletions dfvfs/lib/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
TYPE_INDICATOR_EWF = 'EWF'
TYPE_INDICATOR_EXT = 'EXT'
TYPE_INDICATOR_FAKE = 'FAKE'
TYPE_INDICATOR_FAT = 'FAT'
TYPE_INDICATOR_GPT = 'GPT'
TYPE_INDICATOR_GZIP = 'GZIP'
TYPE_INDICATOR_HFS = 'HFS'
Expand Down Expand Up @@ -90,6 +91,7 @@
TYPE_INDICATOR_APFS,
TYPE_INDICATOR_EXT,
TYPE_INDICATOR_FAKE,
TYPE_INDICATOR_FAT,
TYPE_INDICATOR_HFS,
TYPE_INDICATOR_NTFS,
TYPE_INDICATOR_TSK,
Expand All @@ -116,6 +118,7 @@

# The preferred back-ends.
PREFERRED_EXT_BACK_END = TYPE_INDICATOR_EXT
PREFERRED_FAT_BACK_END = TYPE_INDICATOR_TSK
PREFERRED_GPT_BACK_END = TYPE_INDICATOR_GPT
PREFERRED_HFS_BACK_END = TYPE_INDICATOR_HFS
PREFERRED_NTFS_BACK_END = TYPE_INDICATOR_NTFS
Expand Down
1 change: 1 addition & 0 deletions dfvfs/path/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from dfvfs.path import ewf_path_spec
from dfvfs.path import ext_path_spec
from dfvfs.path import fake_path_spec
from dfvfs.path import fat_path_spec
from dfvfs.path import fvde_path_spec
from dfvfs.path import gpt_path_spec
from dfvfs.path import gzip_path_spec
Expand Down
53 changes: 53 additions & 0 deletions dfvfs/path/fat_path_spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""The FAT path specification implementation."""

from dfvfs.lib import definitions
from dfvfs.path import factory
from dfvfs.path import path_spec


class FATPathSpec(path_spec.PathSpec):
"""FAT path specification implementation.
Attributes:
identifier (int): catalog node identifier (CNID).
location (str): location.
"""

TYPE_INDICATOR = definitions.TYPE_INDICATOR_FAT

def __init__(
self, identifier=None, location=None, parent=None, **kwargs):
"""Initializes a path specification.
Note that an FAT path specification must have a parent.
Args:
identifier (Optional[int]): catalog node identifier (CNID).
location (Optional[str]): location.
parent (Optional[PathSpec]): parent path specification.
Raises:
ValueError: when parent or both identifier and location are not set.
"""
if (not identifier and not location) or not parent:
raise ValueError('Missing identifier and location, or parent value.')

super(FATPathSpec, self).__init__(parent=parent, **kwargs)
self.identifier = identifier
self.location = location

@property
def comparable(self):
"""str: comparable representation of the path specification."""
string_parts = []

if self.identifier is not None:
string_parts.append('identifier: {0:d}'.format(self.identifier))
if self.location is not None:
string_parts.append('location: {0:s}'.format(self.location))

return self._GetComparable(sub_comparable_string=', '.join(string_parts))


factory.Factory.RegisterPathSpec(FATPathSpec)
5 changes: 5 additions & 0 deletions dfvfs/resolver_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@

from dfvfs.resolver_helpers import fake_resolver_helper

try:
from dfvfs.resolver_helpers import fat_resolver_helper
except ImportError:
pass

try:
from dfvfs.resolver_helpers import fvde_resolver_helper
except ImportError:
Expand Down
Loading

0 comments on commit 247a0f9

Please sign in to comment.