From b32088a26ade3dc63a93595d1ff5e701dc774ad0 Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Wed, 21 Sep 2022 06:15:19 +0200 Subject: [PATCH] Changes to expose FAT data stream extents #597 --- .pylintrc | 1 + config/dpkg/control | 2 +- dependencies.ini | 2 +- dfvfs/vfs/fat_file_entry.py | 26 +++++++++++++++++++ requirements.txt | 2 +- setup.cfg | 2 +- setup.py | 22 +++++++--------- tests/vfs/fat_file_entry.py | 27 +++++++++++++++++++ utils/dependencies.py | 52 ++++++++++++++++--------------------- 9 files changed, 91 insertions(+), 45 deletions(-) diff --git a/.pylintrc b/.pylintrc index a0b7540e..02f6254c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -98,6 +98,7 @@ disable=assignment-from-none, locally-disabled, locally-enabled, logging-format-interpolation, + logging-fstring-interpolation, metaclass-assignment, missing-param-doc, no-absolute-import, diff --git a/config/dpkg/control b/config/dpkg/control index 48179d52..b31e3dc9 100644 --- a/config/dpkg/control +++ b/config/dpkg/control @@ -9,7 +9,7 @@ Homepage: https://github.com/log2timeline/dfvfs Package: python3-dfvfs Architecture: all -Depends: libbde-python3 (>= 20220121), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20220709), libfsext-python3 (>= 20220829), libfsfat-python3 (>= 20220816), libfshfs-python3 (>= 20220831), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220829), libfvde-python3 (>= 20220121), libfwnt-python3 (>= 20210717), libluksde-python3 (>= 20220121), libmodi-python3 (>= 20210405), libphdi-python3 (>= 20220228), libqcow-python3 (>= 20201213), libsigscan-python3 (>= 20191221), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-cffi-backend (>= 1.9.1), python3-cryptography (>= 2.0.2), python3-dfdatetime (>= 20220816), python3-dtfabric (>= 20220219), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-pyxattr (>= 0.7.2), python3-yaml (>= 3.10), ${misc:Depends} +Depends: libbde-python3 (>= 20220121), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20220709), libfsext-python3 (>= 20220829), libfsfat-python3 (>= 20220920), libfshfs-python3 (>= 20220831), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220829), libfvde-python3 (>= 20220121), libfwnt-python3 (>= 20210717), libluksde-python3 (>= 20220121), libmodi-python3 (>= 20210405), libphdi-python3 (>= 20220228), libqcow-python3 (>= 20201213), libsigscan-python3 (>= 20191221), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-cffi-backend (>= 1.9.1), python3-cryptography (>= 2.0.2), python3-dfdatetime (>= 20220816), python3-dtfabric (>= 20220219), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-pyxattr (>= 0.7.2), python3-yaml (>= 3.10), ${misc:Depends} Description: Python 3 module of dfVFS dfVFS, or Digital Forensics Virtual File System, provides read-only access to file-system objects from various storage media types and file formats. The goal diff --git a/dependencies.ini b/dependencies.ini index 6ff5441a..df801688 100644 --- a/dependencies.ini +++ b/dependencies.ini @@ -66,7 +66,7 @@ version_property: get_version() [pyfsfat] dpkg_name: libfsfat-python3 l2tbinaries_name: libfsfat -minimum_version: 20220816 +minimum_version: 20220920 pypi_name: libfsfat-python rpm_name: libfsfat-python3 version_property: get_version() diff --git a/dfvfs/vfs/fat_file_entry.py b/dfvfs/vfs/fat_file_entry.py index 5510690c..f254be31 100644 --- a/dfvfs/vfs/fat_file_entry.py +++ b/dfvfs/vfs/fat_file_entry.py @@ -11,6 +11,7 @@ from dfvfs.path import fat_path_spec from dfvfs.resolver import resolver from dfvfs.vfs import attribute +from dfvfs.vfs import extent from dfvfs.vfs import fat_directory from dfvfs.vfs import file_entry @@ -160,6 +161,31 @@ def size(self): """int: size of the file entry in bytes or None if not available.""" return self._fsfat_file_entry.size + def GetExtents(self): + """Retrieves the extents. + + Returns: + list[Extent]: the extents. + """ + if self.entry_type != definitions.FILE_ENTRY_TYPE_FILE: + return [] + + extents = [] + for extent_index in range(self._fsfat_file_entry.number_of_extents): + extent_offset, extent_size, extent_flags = ( + self._fsfat_file_entry.get_extent(extent_index)) + + if extent_flags & 0x1: + extent_type = definitions.EXTENT_TYPE_SPARSE + else: + extent_type = definitions.EXTENT_TYPE_DATA + + data_stream_extent = extent.Extent( + extent_type=extent_type, offset=extent_offset, size=extent_size) + extents.append(data_stream_extent) + + return extents + def GetFileObject(self, data_stream_name=''): """Retrieves a file-like object of a specific data stream. diff --git a/requirements.txt b/requirements.txt index eee47b86..43be1e3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ libbde-python >= 20220121 libewf-python >= 20131210 libfsapfs-python >= 20220709 libfsext-python >= 20220829 -libfsfat-python >= 20220816 +libfsfat-python >= 20220920 libfshfs-python >= 20220831 libfsntfs-python >= 20211229 libfsxfs-python >= 20220829 diff --git a/setup.cfg b/setup.cfg index 6fe8b0a3..149a061f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,7 @@ requires = libbde-python3 >= 20220121 libewf-python3 >= 20131210 libfsapfs-python3 >= 20220709 libfsext-python3 >= 20220829 - libfsfat-python3 >= 20220816 + libfsfat-python3 >= 20220920 libfshfs-python3 >= 20220831 libfsntfs-python3 >= 20211229 libfsxfs-python3 >= 20220829 diff --git a/setup.py b/setup.py index c767a1fd..5e426050 100755 --- a/setup.py +++ b/setup.py @@ -28,9 +28,8 @@ version_tuple = (sys.version_info[0], sys.version_info[1]) if version_tuple < (3, 7): - print(( - 'Unsupported Python version: {0:s}, version 3.7 or higher ' - 'required.').format(sys.version)) + print(f'Unsupported Python version: {sys.version:s}, version 3.7 or higher ' + f'required.') sys.exit(1) # Change PYTHONPATH to include dfvfs so that we can get the version. @@ -87,8 +86,8 @@ def _make_spec_file(self): summary = line[9:] elif line.startswith('BuildRequires: '): - line = 'BuildRequires: {0:s}-setuptools, {0:s}-devel'.format( - python_package) + line = (f'BuildRequires: {python_package:s}-setuptools, ' + f'{python_package:s}-devel') elif line.startswith('Requires: '): requires = line[10:] @@ -111,7 +110,7 @@ def _make_spec_file(self): elif line.startswith('%files'): lines = [ - '%files -n {0:s}-%{{name}}'.format(python_package), + f'%files -n {python_package:s}-%{{name}}', '%defattr(644,root,root,755)', '%license LICENSE', '%doc ACKNOWLEDGEMENTS AUTHORS README'] @@ -132,17 +131,16 @@ def _make_spec_file(self): elif line.startswith('%prep'): in_description = False - python_spec_file.append( - '%package -n {0:s}-%{{name}}'.format(python_package)) - python_summary = 'Python 3 module of {0:s}'.format(summary) + python_spec_file.append(f'%package -n {python_package:s}-%{{name}}') + python_summary = f'Python 3 module of {summary:s}' if requires: - python_spec_file.append('Requires: {0:s}'.format(requires)) + python_spec_file.append(f'Requires: {requires:s}') python_spec_file.extend([ - 'Summary: {0:s}'.format(python_summary), + f'Summary: {python_summary:s}', '', - '%description -n {0:s}-%{{name}}'.format(python_package)]) + f'%description -n {python_package:s}-%{{name}}']) python_spec_file.extend(description) diff --git a/tests/vfs/fat_file_entry.py b/tests/vfs/fat_file_entry.py index c27240b2..454ff66e 100644 --- a/tests/vfs/fat_file_entry.py +++ b/tests/vfs/fat_file_entry.py @@ -210,6 +210,33 @@ def testGetDataStream(self): data_stream = file_entry.GetDataStream('') self.assertIsNotNone(data_stream) + def testGetExtents(self): + """Tests the GetExtents function.""" + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_FAT, + identifier=self._IDENTIFIER_ANOTHER_FILE, + location='\\a_directory\\another_file', + parent=self._raw_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + extents = file_entry.GetExtents() + self.assertEqual(len(extents), 1) + + self.assertEqual(extents[0].extent_type, definitions.EXTENT_TYPE_DATA) + self.assertEqual(extents[0].offset, 31232) + # TODO: change libfsfat/pyfsfat to return cluster block size + self.assertEqual(extents[0].size, 22) + + path_spec = path_spec_factory.Factory.NewPathSpec( + definitions.TYPE_INDICATOR_FAT, identifier=self._IDENTIFIER_A_DIRECTORY, + location='\\a_directory', parent=self._raw_path_spec) + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + self.assertIsNotNone(file_entry) + + extents = file_entry.GetExtents() + self.assertEqual(len(extents), 0) + def testGetFileEntryByPathSpec(self): """Tests the GetFileEntryByPathSpec function.""" path_spec = path_spec_factory.Factory.NewPathSpec( diff --git a/utils/dependencies.py b/utils/dependencies.py index 6034a996..a2c40405 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -157,8 +157,7 @@ def _CheckPythonModule(self, dependency): """ module_object = self._ImportPythonModule(dependency.name) if not module_object: - status_message = 'missing: {0:s}'.format(dependency.name) - return False, status_message + return False, f'missing: {dependency.name:s}' if not dependency.version_property: return True, dependency.name @@ -196,13 +195,11 @@ def _CheckPythonModuleVersion( module_version = version_method() if not module_version: - status_message = ( - 'unable to determine version information for: {0:s}').format( - module_name) - return False, status_message + return False, ( + f'unable to determine version information for: {module_name:s}') # Make sure the module version is a string. - module_version = '{0!s}'.format(module_version) + module_version = f'{module_version!s}' # Split the version string and convert every digit into an integer. # A string compare of both version strings will yield an incorrect result. @@ -217,42 +214,38 @@ def _CheckPythonModuleVersion( module_version_map = list( map(int, self._VERSION_SPLIT_REGEX.split(module_version))) except ValueError: - status_message = 'unable to parse module version: {0:s} {1:s}'.format( - module_name, module_version) - return False, status_message + return False, ( + f'unable to parse module version: {module_name:s} {module_version:s}') if minimum_version: try: minimum_version_map = list( map(int, self._VERSION_SPLIT_REGEX.split(minimum_version))) except ValueError: - status_message = 'unable to parse minimum version: {0:s} {1:s}'.format( - module_name, minimum_version) - return False, status_message + return False, ( + f'unable to parse minimum version: {module_name:s} ' + f'{minimum_version:s}') if module_version_map < minimum_version_map: - status_message = ( - '{0:s} version: {1!s} is too old, {2!s} or later required').format( - module_name, module_version, minimum_version) - return False, status_message + return False, ( + f'{module_name:s} version: {module_version!s} is too old, ' + f'{minimum_version!s} or later required') if maximum_version: try: maximum_version_map = list( map(int, self._VERSION_SPLIT_REGEX.split(maximum_version))) except ValueError: - status_message = 'unable to parse maximum version: {0:s} {1:s}'.format( - module_name, maximum_version) - return False, status_message + return False, ( + f'unable to parse maximum version: {module_name:s} ' + f'{maximum_version:s}') if module_version_map > maximum_version_map: - status_message = ( - '{0:s} version: {1!s} is too recent, {2!s} or earlier ' - 'required').format(module_name, module_version, maximum_version) - return False, status_message + return False, ( + f'{module_name:s} version: {module_version!s} is too recent, ' + f'{maximum_version!s} or earlier required') - status_message = '{0:s} version: {1!s}'.format(module_name, module_version) - return True, status_message + return True, f'{module_name:s} version: {module_version!s}' def _ImportPythonModule(self, module_name): """Imports a Python module. @@ -292,10 +285,10 @@ def _PrintCheckDependencyStatus( else: status_indicator = '[FAILURE]' - print('{0:s}\t{1:s}'.format(status_indicator, status_message)) + print(f'{status_indicator:s}\t{status_message:s}') elif verbose_output: - print('[OK]\t\t{0:s}'.format(status_message)) + print(f'[OK]\t\t{status_message:s}') def CheckDependencies(self, verbose_output=True): """Checks the availability of the dependencies. @@ -349,7 +342,8 @@ def CheckTestDependencies(self, verbose_output=True): continue result, status_message = self._CheckPythonModule(dependency) - if not result: + + if not result and not dependency.is_optional: check_result = False self._PrintCheckDependencyStatus(