From 17b27b51b3a1dc5628bcb8f949f5badd290890df Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Tue, 13 Nov 2018 07:27:29 +0100 Subject: [PATCH 1/3] Added APFS support --- .pylintrc | 2 +- appveyor.yml | 4 +- config/dpkg/control | 4 +- config/linux/gift_copr_install.sh | 3 + config/linux/gift_ppa_install.sh | 3 + config/linux/gift_ppa_install_py3.sh | 3 + config/travis/install.sh | 6 +- dependencies.ini | 10 +- dfvfs/file_io/apfs_file_io.py | 155 +++++++++ dfvfs/file_io/file_object_io.py | 1 + dfvfs/lib/apfs_container.py | 32 ++ dfvfs/lib/definitions.py | 5 + dfvfs/lib/vshadow.py | 2 +- dfvfs/path/__init__.py | 2 + dfvfs/path/apfs_container_path_spec.py | 55 ++++ dfvfs/path/apfs_path_spec.py | 55 ++++ dfvfs/resolver_helpers/__init__.py | 3 + .../apfs_container_resolver_helper.py | 29 ++ .../resolver_helpers/apfs_resolver_helper.py | 41 +++ dfvfs/vfs/apfs_container_file_entry.py | 170 ++++++++++ dfvfs/vfs/apfs_container_file_system.py | 157 ++++++++++ dfvfs/vfs/apfs_file_entry.py | 256 +++++++++++++++ dfvfs/vfs/apfs_file_system.py | 177 +++++++++++ dfvfs/vfs/ntfs_file_system.py | 5 +- requirements.txt | 3 +- setup.cfg | 3 +- test_data/apfs.dmg | Bin 0 -> 4194304 bytes test_data/apfs_encrypted.dmg | Bin 0 -> 4194304 bytes tests/file_io/apfs_file_io.py | 138 ++++++++ tests/lib/apfs_container.py | 54 ++++ tests/path/apfs_container_path_spec.py | 98 ++++++ tests/path/apfs_path_spec.py | 86 +++++ tests/vfs/apfs_container_file_entry.py | 181 +++++++++++ tests/vfs/apfs_container_file_system.py | 147 +++++++++ tests/vfs/apfs_file_entry.py | 294 ++++++++++++++++++ tests/vfs/apfs_file_system.py | 115 +++++++ tests/vfs/ntfs_file_entry.py | 2 +- 37 files changed, 2286 insertions(+), 15 deletions(-) create mode 100644 dfvfs/file_io/apfs_file_io.py create mode 100644 dfvfs/lib/apfs_container.py create mode 100644 dfvfs/path/apfs_container_path_spec.py create mode 100644 dfvfs/path/apfs_path_spec.py create mode 100644 dfvfs/resolver_helpers/apfs_container_resolver_helper.py create mode 100644 dfvfs/resolver_helpers/apfs_resolver_helper.py create mode 100644 dfvfs/vfs/apfs_container_file_entry.py create mode 100644 dfvfs/vfs/apfs_container_file_system.py create mode 100644 dfvfs/vfs/apfs_file_entry.py create mode 100644 dfvfs/vfs/apfs_file_system.py create mode 100644 test_data/apfs.dmg create mode 100644 test_data/apfs_encrypted.dmg create mode 100644 tests/file_io/apfs_file_io.py create mode 100644 tests/lib/apfs_container.py create mode 100644 tests/path/apfs_container_path_spec.py create mode 100644 tests/path/apfs_path_spec.py create mode 100644 tests/vfs/apfs_container_file_entry.py create mode 100644 tests/vfs/apfs_container_file_system.py create mode 100644 tests/vfs/apfs_file_entry.py create mode 100644 tests/vfs/apfs_file_system.py diff --git a/.pylintrc b/.pylintrc index 0bd6276c..97a8fcaa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code -extension-pkg-whitelist=pybde,pyewf,pyfsntfs,pyfvde,pyfwnt,pyqcow,pysigscan,pysmdev,pysmraw,pytsk3,pyvhdi,pyvmdk,pyvshadow,pyvslvm +extension-pkg-whitelist=pybde,pyewf,pyfsapfs,pyfsntfs,pyfvde,pyfwnt,pyqcow,pysigscan,pysmdev,pysmraw,pytsk3,pyvhdi,pyvmdk,pyvshadow,pyvslvm # Add files or directories to the blacklist. They should be base names, not # paths. diff --git a/appveyor.yml b/appveyor.yml index 71f346d1..d438ffc3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,11 +26,11 @@ install: - cmd: if [%TARGET%]==[python27] ( mkdir dependencies && set PYTHONPATH=..\l2tdevtools && - "%PYTHON%\\python.exe" ..\l2tdevtools\tools\update.py --download-directory dependencies --machine-type %MACHINE_TYPE% --msi-targetdir "%PYTHON%" --track dev PyYAML dfdatetime dtfabric funcsigs libbde libewf libfsntfs libfvde libfwnt libqcow libsigscan libsmdev libsmraw libvhdi libvmdk libvshadow libvslvm mock pbr pycrypto pysqlite pytsk3 six ) + "%PYTHON%\\python.exe" ..\l2tdevtools\tools\update.py --download-directory dependencies --machine-type %MACHINE_TYPE% --msi-targetdir "%PYTHON%" --track dev PyYAML dfdatetime dtfabric funcsigs libbde libewf libfsapfs libfsntfs libfvde libfwnt libqcow libsigscan libsmdev libsmraw libvhdi libvmdk libvshadow libvslvm mock pbr pycrypto pysqlite pytsk3 six ) - cmd: if [%TARGET%]==[python36] ( mkdir dependencies && set PYTHONPATH=..\l2tdevtools && - "%PYTHON%\\python.exe" ..\l2tdevtools\tools\update.py --download-directory dependencies --machine-type %MACHINE_TYPE% --msi-targetdir "%PYTHON%" --track dev PyYAML dfdatetime dtfabric funcsigs libbde libewf libfsntfs libfvde libfwnt libqcow libsigscan libsmdev libsmraw libvhdi libvmdk libvshadow libvslvm mock pbr pycrypto pytsk3 six ) + "%PYTHON%\\python.exe" ..\l2tdevtools\tools\update.py --download-directory dependencies --machine-type %MACHINE_TYPE% --msi-targetdir "%PYTHON%" --track dev PyYAML dfdatetime dtfabric funcsigs libbde libewf libfsapfs libfsntfs libfvde libfwnt libqcow libsigscan libsmdev libsmraw libvhdi libvmdk libvshadow libvslvm mock pbr pycrypto pytsk3 six ) build: off diff --git a/config/dpkg/control b/config/dpkg/control index 8ea20cf3..92eff89f 100644 --- a/config/dpkg/control +++ b/config/dpkg/control @@ -10,7 +10,7 @@ Homepage: https://github.com/log2timeline/dfvfs Package: python-dfvfs Architecture: all -Depends: libbde-python (>= 20140531), libewf-python (>= 20131210), libfsntfs-python (>= 20151130), libfvde-python (>= 20160719), libfwnt-python (>= 20160418), libqcow-python (>= 20131204), libsigscan-python (>= 20150627), libsmdev-python (>= 20140529), libsmraw-python (>= 20140612), libvhdi-python (>= 20131210), libvmdk-python (>= 20140421), libvshadow-python (>= 20160109), libvslvm-python (>= 20160109), python-backports.lzma, python-crypto (>= 2.6), python-dfdatetime (>= 20180324), python-dtfabric (>= 20170524), python-pysqlite2, python-pytsk3 (>= 20160721), python-yaml (>= 3.10), ${python:Depends}, ${misc:Depends} +Depends: libbde-python (>= 20140531), libewf-python (>= 20131210), libfsapfs-python (>= 20181110), libfsntfs-python (>= 20151130), libfvde-python (>= 20160719), libfwnt-python (>= 20160418), libqcow-python (>= 20131204), libsigscan-python (>= 20150627), libsmdev-python (>= 20140529), libsmraw-python (>= 20140612), libvhdi-python (>= 20131210), libvmdk-python (>= 20140421), libvshadow-python (>= 20160109), libvslvm-python (>= 20160109), python-backports.lzma, python-crypto (>= 2.6), python-dfdatetime (>= 20181025), python-dtfabric (>= 20170524), python-pysqlite2, python-pytsk3 (>= 20160721), python-yaml (>= 3.10), ${python:Depends}, ${misc:Depends} Description: Python 2 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 @@ -20,7 +20,7 @@ Description: Python 2 module of dfVFS Package: python3-dfvfs Architecture: all -Depends: libbde-python3 (>= 20140531), libewf-python3 (>= 20131210), libfsntfs-python3 (>= 20151130), libfvde-python3 (>= 20160719), libfwnt-python3 (>= 20160418), libqcow-python3 (>= 20131204), libsigscan-python3 (>= 20150627), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20131210), libvmdk-python3 (>= 20140421), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-crypto (>= 2.6), python3-dfdatetime (>= 20180324), python3-dtfabric (>= 20170524), python3-pytsk3 (>= 20160721), python3-yaml (>= 3.10), ${python3:Depends}, ${misc:Depends} +Depends: libbde-python3 (>= 20140531), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20181110), libfsntfs-python3 (>= 20151130), libfvde-python3 (>= 20160719), libfwnt-python3 (>= 20160418), libqcow-python3 (>= 20131204), libsigscan-python3 (>= 20150627), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20131210), libvmdk-python3 (>= 20140421), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-crypto (>= 2.6), python3-dfdatetime (>= 20181025), python3-dtfabric (>= 20170524), python3-pytsk3 (>= 20160721), python3-yaml (>= 3.10), ${python3:Depends}, ${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/config/linux/gift_copr_install.sh b/config/linux/gift_copr_install.sh index c7853128..76c88e55 100644 --- a/config/linux/gift_copr_install.sh +++ b/config/linux/gift_copr_install.sh @@ -11,6 +11,7 @@ set -e PYTHON2_DEPENDENCIES="PyYAML libbde-python libewf-python + libfsapfs-python2 libfsntfs-python libfvde-python libfwnt-python @@ -40,6 +41,8 @@ DEBUG_DEPENDENCIES="libbde-debuginfo libbde-python-debuginfo libewf-debuginfo libewf-python-debuginfo + libfsapfs-debuginfo + libfsapfs-python2-debuginfo libfsntfs-debuginfo libfsntfs-python-debuginfo libfvde-debuginfo diff --git a/config/linux/gift_ppa_install.sh b/config/linux/gift_ppa_install.sh index 1ae481cd..c5102242 100755 --- a/config/linux/gift_ppa_install.sh +++ b/config/linux/gift_ppa_install.sh @@ -10,6 +10,7 @@ set -e # This should not include packages only required for testing or development. PYTHON2_DEPENDENCIES="libbde-python libewf-python + libfsapfs-python libfsntfs-python libfvde-python libfwnt-python @@ -40,6 +41,8 @@ DEBUG_DEPENDENCIES="libbde-dbg libbde-python-dbg libewf-dbg libewf-python-dbg + libfsapfs-dbg + libfsapfs-python-dbg libfsntfs-dbg libfsntfs-python-dbg libfvde-dbg diff --git a/config/linux/gift_ppa_install_py3.sh b/config/linux/gift_ppa_install_py3.sh index 6039c230..74316cf8 100644 --- a/config/linux/gift_ppa_install_py3.sh +++ b/config/linux/gift_ppa_install_py3.sh @@ -10,6 +10,7 @@ set -e # This should not include packages only required for testing or development. PYTHON3_DEPENDENCIES="libbde-python3 libewf-python3 + libfsapfs-python3 libfsntfs-python3 libfvde-python3 libfwnt-python3 @@ -38,6 +39,8 @@ DEBUG_DEPENDENCIES="libbde-dbg libbde-python3-dbg libewf-dbg libewf-python3-dbg + libfsapfs-dbg + libfsapfs-python3-dbg libfsntfs-dbg libfsntfs-python3-dbg libfvde-dbg diff --git a/config/travis/install.sh b/config/travis/install.sh index 92402e0e..3a19b0df 100755 --- a/config/travis/install.sh +++ b/config/travis/install.sh @@ -5,15 +5,15 @@ # This file is generated by l2tdevtools update-dependencies.py any dependency # related changes should be made in dependencies.ini. -L2TBINARIES_DEPENDENCIES="PyYAML backports.lzma dfdatetime dtfabric libbde libewf libfsntfs libfvde libfwnt libqcow libsigscan libsmdev libsmraw libvhdi libvmdk libvshadow libvslvm pycrypto pysqlite pytsk3"; +L2TBINARIES_DEPENDENCIES="PyYAML backports.lzma dfdatetime dtfabric libbde libewf libfsapfs libfsntfs libfvde libfwnt libqcow libsigscan libsmdev libsmraw libvhdi libvmdk libvshadow libvslvm pycrypto pysqlite pytsk3"; L2TBINARIES_TEST_DEPENDENCIES="funcsigs mock pbr six"; -PYTHON2_DEPENDENCIES="libbde-python libewf-python libfsntfs-python libfvde-python libfwnt-python libqcow-python libsigscan-python libsmdev-python libsmraw-python libvhdi-python libvmdk-python libvshadow-python libvslvm-python python-backports.lzma python-crypto python-dfdatetime python-dtfabric python-pysqlite2 python-pytsk3 python-yaml"; +PYTHON2_DEPENDENCIES="libbde-python libewf-python libfsapfs-python libfsntfs-python libfvde-python libfwnt-python libqcow-python libsigscan-python libsmdev-python libsmraw-python libvhdi-python libvmdk-python libvshadow-python libvslvm-python python-backports.lzma python-crypto python-dfdatetime python-dtfabric python-pysqlite2 python-pytsk3 python-yaml"; PYTHON2_TEST_DEPENDENCIES="python-coverage python-mock python-tox"; -PYTHON3_DEPENDENCIES="libbde-python3 libewf-python3 libfsntfs-python3 libfvde-python3 libfwnt-python3 libqcow-python3 libsigscan-python3 libsmdev-python3 libsmraw-python3 libvhdi-python3 libvmdk-python3 libvshadow-python3 libvslvm-python3 python3-crypto python3-dfdatetime python3-dtfabric python3-pytsk3 python3-yaml"; +PYTHON3_DEPENDENCIES="libbde-python3 libewf-python3 libfsapfs-python3 libfsntfs-python3 libfvde-python3 libfwnt-python3 libqcow-python3 libsigscan-python3 libsmdev-python3 libsmraw-python3 libvhdi-python3 libvmdk-python3 libvshadow-python3 libvslvm-python3 python3-crypto python3-dfdatetime python3-dtfabric python3-pytsk3 python3-yaml"; PYTHON3_TEST_DEPENDENCIES="python3-mock python3-setuptools python3-tox"; diff --git a/dependencies.ini b/dependencies.ini index 87fb7f8a..04b912db 100644 --- a/dependencies.ini +++ b/dependencies.ini @@ -8,7 +8,7 @@ version_property: __version__ [dfdatetime] dpkg_name: python-dfdatetime -minimum_version: 20180324 +minimum_version: 20181025 rpm_name: python-dfdatetime version_property: __version__ @@ -43,6 +43,14 @@ pypi_name: libewf-python rpm_name: libewf-python version_property: get_version() +[pyfsapfs] +dpkg_name: libfsapfs-python +l2tbinaries_name: libfsapfs +minimum_version: 20181110 +pypi_name: libfsapfs-python +rpm_name: libfsapfs-python2 +version_property: get_version() + [pyfsntfs] dpkg_name: libfsntfs-python l2tbinaries_name: libfsntfs diff --git a/dfvfs/file_io/apfs_file_io.py b/dfvfs/file_io/apfs_file_io.py new file mode 100644 index 00000000..69025d92 --- /dev/null +++ b/dfvfs/file_io/apfs_file_io.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +"""The APFS file-like object implementation.""" + +from __future__ import unicode_literals + +import os + +from dfvfs.file_io import file_io +from dfvfs.resolver import resolver + + +class APFSFile(file_io.FileIO): + """File-like object using pyfsapfs.""" + + def __init__(self, resolver_context): + """Initializes a file-like object. + + Args: + resolver_context (Context): resolver context. + """ + super(APFSFile, self).__init__(resolver_context) + self._file_system = None + self._fsapfs_data_stream = None + self._fsapfs_file_entry = None + + def _Close(self): + """Closes the file-like object.""" + self._fsapfs_data_stream = None + self._fsapfs_file_entry = None + + self._file_system.Close() + self._file_system = None + + def _Open(self, path_spec=None, mode='rb'): + """Opens the file-like object defined by path specification. + + Args: + path_spec (PathSpec): path specification. + 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. + OSError: if the file-like object could not be opened. + PathSpecError: if the path specification is incorrect. + ValueError: if the path specification is invalid. + """ + if not path_spec: + raise ValueError('Missing path specification.') + + data_stream = getattr(path_spec, 'data_stream', None) + + self._file_system = resolver.Resolver.OpenFileSystem( + path_spec, resolver_context=self._resolver_context) + + file_entry = self._file_system.GetFileEntryByPathSpec(path_spec) + if not file_entry: + raise IOError('Unable to open file entry.') + + fsapfs_data_stream = None + fsapfs_file_entry = file_entry.GetAPFSFileEntry() + if not fsapfs_file_entry: + raise IOError('Unable to open APFS file entry.') + + if data_stream: + fsapfs_data_stream = fsapfs_file_entry.get_alternate_data_stream_by_name( + data_stream) + if not fsapfs_data_stream: + raise IOError('Unable to open data stream: {0:s}.'.format( + data_stream)) + + self._fsapfs_data_stream = fsapfs_data_stream + self._fsapfs_file_entry = fsapfs_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._fsapfs_data_stream: + return self._fsapfs_data_stream.read(size=size) + return self._fsapfs_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._fsapfs_data_stream: + self._fsapfs_data_stream.seek(offset, whence) + else: + self._fsapfs_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._fsapfs_data_stream: + return self._fsapfs_data_stream.get_offset() + return self._fsapfs_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._fsapfs_data_stream: + return self._fsapfs_data_stream.get_size() + return self._fsapfs_file_entry.get_size() diff --git a/dfvfs/file_io/file_object_io.py b/dfvfs/file_io/file_object_io.py index 79c089bd..243d89cd 100644 --- a/dfvfs/file_io/file_object_io.py +++ b/dfvfs/file_io/file_object_io.py @@ -65,6 +65,7 @@ def _Open(self, path_spec=None, mode='rb'): if not self._file_object: raise IOError('Unable to open missing file-like object.') + # pylint: disable=redundant-returns-doc @abc.abstractmethod def _OpenFileObject(self, path_spec): """Opens the file-like object defined by path specification. diff --git a/dfvfs/lib/apfs_container.py b/dfvfs/lib/apfs_container.py new file mode 100644 index 00000000..1ae19b81 --- /dev/null +++ b/dfvfs/lib/apfs_container.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +"""Helper functions for APFS container support.""" + +from __future__ import unicode_literals + + +def APFSContainerPathSpecGetVolumeIndex(path_spec): + """Retrieves the volume index from the path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + int: volume index. + """ + volume_index = getattr(path_spec, 'volume_index', None) + if volume_index is not None: + return volume_index + + location = getattr(path_spec, 'location', None) + if location is None or not location.startswith('/apfs'): + return None + + try: + volume_index = int(location[5:], 10) - 1 + except (TypeError, ValueError): + volume_index = None + + if volume_index is None or volume_index < 0 or volume_index > 99: + volume_index = None + + return volume_index diff --git a/dfvfs/lib/definitions.py b/dfvfs/lib/definitions.py index 89a0c31a..fc3d6259 100644 --- a/dfvfs/lib/definitions.py +++ b/dfvfs/lib/definitions.py @@ -29,6 +29,8 @@ ENCRYPTION_MODE_OFB = 'ofb' # The type indicator definitions. +TYPE_INDICATOR_APFS = 'APFS' +TYPE_INDICATOR_APFS_CONTAINER = 'APFS_CONTAINER' TYPE_INDICATOR_BDE = 'BDE' TYPE_INDICATOR_BZIP2 = 'BZIP2' TYPE_INDICATOR_COMPRESSED_STREAM = 'COMPRESSED_STREAM' @@ -60,6 +62,7 @@ TYPE_INDICATOR_FVDE]) FILE_SYSTEM_TYPE_INDICATORS = frozenset([ + TYPE_INDICATOR_APFS, TYPE_INDICATOR_NTFS, TYPE_INDICATOR_TSK]) @@ -71,6 +74,7 @@ TYPE_INDICATOR_VMDK]) VOLUME_SYSTEM_TYPE_INDICATORS = frozenset([ + TYPE_INDICATOR_APFS_CONTAINER, TYPE_INDICATOR_LVM, TYPE_INDICATOR_TSK_PARTITION, TYPE_INDICATOR_VSHADOW]) @@ -91,6 +95,7 @@ FILE_ENTRY_TYPE_LINK = 'link' FILE_ENTRY_TYPE_SOCKET = 'socket' FILE_ENTRY_TYPE_PIPE = 'pipe' +FILE_ENTRY_TYPE_WHITEOUT = 'whiteout' # The format category definitions. FORMAT_CATEGORY_UNDEFINED = 0 diff --git a/dfvfs/lib/vshadow.py b/dfvfs/lib/vshadow.py index 424d0323..492fc362 100644 --- a/dfvfs/lib/vshadow.py +++ b/dfvfs/lib/vshadow.py @@ -24,7 +24,7 @@ def VShadowPathSpecGetStoreIndex(path_spec): store_index = None try: store_index = int(location[4:], 10) - 1 - except ValueError: + except (TypeError, ValueError): pass if store_index is None or store_index < 0: diff --git a/dfvfs/path/__init__.py b/dfvfs/path/__init__.py index 7cc88d02..30f79977 100644 --- a/dfvfs/path/__init__.py +++ b/dfvfs/path/__init__.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """Imports for path specification factory.""" +from dfvfs.path import apfs_container_path_spec +from dfvfs.path import apfs_path_spec from dfvfs.path import bde_path_spec from dfvfs.path import compressed_stream_path_spec from dfvfs.path import cpio_path_spec diff --git a/dfvfs/path/apfs_container_path_spec.py b/dfvfs/path/apfs_container_path_spec.py new file mode 100644 index 00000000..0510d520 --- /dev/null +++ b/dfvfs/path/apfs_container_path_spec.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +"""The APFS container path specification implementation.""" + +from __future__ import unicode_literals + +from dfvfs.lib import definitions +from dfvfs.path import factory +from dfvfs.path import path_spec + + +class APFSContainerPathSpec(path_spec.PathSpec): + """APFS container path specification implementation. + + Attributes: + location (str): location. + volume_index (int): volume index. + """ + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS_CONTAINER + + def __init__( + self, location=None, parent=None, volume_index=None, **kwargs): + """Initializes a path specification. + + Note that the APFS container path specification must have a parent. + + Args: + location (Optional[str]): location. + parent (Optional[PathSpec]): parent path specification. + volume_index (Optional[int]): index of the volume within the container. + + Raises: + ValueError: when parent is not set. + """ + if not parent: + raise ValueError('Missing parent value.') + + super(APFSContainerPathSpec, self).__init__(parent=parent, **kwargs) + self.location = location + self.volume_index = volume_index + + @property + def comparable(self): + """str: comparable representation of the path specification.""" + string_parts = [] + + if self.location is not None: + string_parts.append('location: {0:s}'.format(self.location)) + if self.volume_index is not None: + string_parts.append('volume index: {0:d}'.format(self.volume_index)) + + return self._GetComparable(sub_comparable_string=', '.join(string_parts)) + + +factory.Factory.RegisterPathSpec(APFSContainerPathSpec) diff --git a/dfvfs/path/apfs_path_spec.py b/dfvfs/path/apfs_path_spec.py new file mode 100644 index 00000000..2d4093c3 --- /dev/null +++ b/dfvfs/path/apfs_path_spec.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +"""The APFS path specification implementation.""" + +from __future__ import unicode_literals + +from dfvfs.lib import definitions +from dfvfs.path import factory +from dfvfs.path import path_spec + + +class APFSPathSpec(path_spec.PathSpec): + """APFS path specification implementation. + + Attributes: + identifier (int): identifier. + location (str): location. + """ + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS + + def __init__( + self, identifier=None, location=None, parent=None, **kwargs): + """Initializes a path specification. + + Note that the APFS path specification must have a parent. + + Args: + identifier (Optional[int]): identifier. + location (Optional[str]): location. + parent (Optional[PathSpec]): parent path specification. + + Raises: + ValueError: when identifier and location, or parent are not set. + """ + if (not identifier and not location) or not parent: + raise ValueError('Missing identifier and location, or parent value.') + + super(APFSPathSpec, 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(APFSPathSpec) diff --git a/dfvfs/resolver_helpers/__init__.py b/dfvfs/resolver_helpers/__init__.py index 8617041c..8f1061a4 100644 --- a/dfvfs/resolver_helpers/__init__.py +++ b/dfvfs/resolver_helpers/__init__.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- """Imports for the path specification resolver.""" +from dfvfs.resolver_helpers import apfs_container_resolver_helper +from dfvfs.resolver_helpers import apfs_resolver_helper + try: from dfvfs.resolver_helpers import bde_resolver_helper except ImportError: diff --git a/dfvfs/resolver_helpers/apfs_container_resolver_helper.py b/dfvfs/resolver_helpers/apfs_container_resolver_helper.py new file mode 100644 index 00000000..f8913d0d --- /dev/null +++ b/dfvfs/resolver_helpers/apfs_container_resolver_helper.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +"""The APFS container path specification resolver helper implementation.""" + +from __future__ import unicode_literals + +from dfvfs.lib import definitions +from dfvfs.resolver_helpers import manager +from dfvfs.resolver_helpers import resolver_helper +from dfvfs.vfs import apfs_container_file_system + + +class APFSContainerResolverHelper(resolver_helper.ResolverHelper): + """APFS container resolver helper.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS_CONTAINER + + def NewFileSystem(self, resolver_context): + """Creates a new file system object. + + Args: + resolver_context (Context): resolver context. + + Returns: + APFSContainerFileSystem: file system. + """ + return apfs_container_file_system.APFSContainerFileSystem(resolver_context) + + +manager.ResolverHelperManager.RegisterHelper(APFSContainerResolverHelper()) diff --git a/dfvfs/resolver_helpers/apfs_resolver_helper.py b/dfvfs/resolver_helpers/apfs_resolver_helper.py new file mode 100644 index 00000000..d91f02d6 --- /dev/null +++ b/dfvfs/resolver_helpers/apfs_resolver_helper.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +"""The APFS path specification resolver helper implementation.""" + +from __future__ import unicode_literals + +from dfvfs.file_io import apfs_file_io +from dfvfs.lib import definitions +from dfvfs.resolver_helpers import manager +from dfvfs.resolver_helpers import resolver_helper +from dfvfs.vfs import apfs_file_system + + +class APFSResolverHelper(resolver_helper.ResolverHelper): + """APFS resolver helper.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS + + def NewFileObject(self, resolver_context): + """Creates a new file-like object. + + Args: + resolver_context (Context): resolver context. + + Returns: + FileIO: file-like object. + """ + return apfs_file_io.APFSFile(resolver_context) + + def NewFileSystem(self, resolver_context): + """Creates a new file system object. + + Args: + resolver_context (Context): resolver context. + + Returns: + FileSystem: file system. + """ + return apfs_file_system.APFSFileSystem(resolver_context) + + +manager.ResolverHelperManager.RegisterHelper(APFSResolverHelper()) diff --git a/dfvfs/vfs/apfs_container_file_entry.py b/dfvfs/vfs/apfs_container_file_entry.py new file mode 100644 index 00000000..3fb1b497 --- /dev/null +++ b/dfvfs/vfs/apfs_container_file_entry.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +"""The APFS container file entry implementation.""" + +from __future__ import unicode_literals + +from dfvfs.lib import apfs_container +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.path import apfs_container_path_spec +from dfvfs.vfs import file_entry + + +class APFSContainerDirectory(file_entry.Directory): + """File system directory that uses pyfsapfs.""" + + def _EntriesGenerator(self): + """Retrieves directory entries. + + Since a directory can contain a vast number of entries using + a generator is more memory efficient. + + Yields: + APFSContainerPathSpec: a path specification. + """ + # Only the virtual root file has directory entries. + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex( + self.path_spec) + if volume_index is not None: + return + + location = getattr(self.path_spec, 'location', None) + if location is None or location != self._file_system.LOCATION_ROOT: + return + + fsapfs_container = self._file_system.GetAPFSContainer() + + for volume_index in range(0, fsapfs_container.number_of_volumes): + yield apfs_container_path_spec.APFSContainerPathSpec( + location='/apfs{0:d}'.format(volume_index + 1), + volume_index=volume_index, parent=self.path_spec.parent) + + +class APFSContainerFileEntry(file_entry.FileEntry): + """File system file entry that uses pyfsapfs.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS_CONTAINER + + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False): + """Initializes a file entry. + + Args: + resolver_context (Context): resolver context. + file_system (FileSystem): 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. + + Raises: + BackEndError: when the fsapfs volume is missing in a non-virtual + file entry. + """ + fsapfs_volume = file_system.GetAPFSVolumeByPathSpec(path_spec) + if not is_virtual and fsapfs_volume is None: + raise errors.BackEndError( + 'Missing fsapfs volume in non-virtual file entry.') + + super(APFSContainerFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._name = None + self._fsapfs_volume = fsapfs_volume + + if self._is_virtual: + self.entry_type = definitions.FILE_ENTRY_TYPE_DIRECTORY + else: + self.entry_type = definitions.FILE_ENTRY_TYPE_FILE + + def _GetDirectory(self): + """Retrieves a directory. + + Returns: + APFSContainerDirectory: a directory or None if not available. + """ + if self.entry_type != definitions.FILE_ENTRY_TYPE_DIRECTORY: + return None + + return APFSContainerDirectory(self._file_system, self.path_spec) + + def _GetStat(self): + """Retrieves information about the file entry. + + Returns: + VFSStat: a stat object. + """ + stat_object = super(APFSContainerFileEntry, self)._GetStat() + + if self._fsapfs_volume is not None: + # File data stat information. + # TODO: implement volume size. + # stat_object.size = self._fsapfs_volume.size + pass + + # Ownership and permissions stat information. + + # File entry type stat information. + + # The root file entry is virtual and should have type directory. + return stat_object + + def _GetSubFileEntries(self): + """Retrieves a sub file entries generator. + + Yields: + APFSContainerFileEntry: a sub file entry. + """ + if self._directory is None: + self._directory = self._GetDirectory() + + if self._directory: + for path_spec in self._directory.entries: + yield APFSContainerFileEntry( + self._resolver_context, self._file_system, path_spec) + + # TODO: expose date and time values. + + @property + def name(self): + """str: name of the file entry, which does not include the full path.""" + if self._name is None: + location = getattr(self.path_spec, 'location', None) + if location is not None: + self._name = self._file_system.BasenamePath(location) + else: + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex( + self.path_spec) + if volume_index is not None: + self._name = 'apfs{0:d}'.format(volume_index + 1) + else: + self._name = '' + return self._name + + @property + def sub_file_entries(self): + """generator[APFSContainerFileEntry]: sub file entries.""" + return self._GetSubFileEntries() + + def GetAPFSVolume(self): + """Retrieves an APFS volume. + + Returns: + pyfsapfs.volume: an APFS volume or None if not available. + """ + return self._fsapfs_volume + + def GetParentFileEntry(self): + """Retrieves the parent file entry. + + Returns: + APFSContainerFileEntry: parent file entry or None if not available. + """ + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex( + self.path_spec) + if volume_index is None: + return None + + return self._file_system.GetRootFileEntry() diff --git a/dfvfs/vfs/apfs_container_file_system.py b/dfvfs/vfs/apfs_container_file_system.py new file mode 100644 index 00000000..14fc9cfc --- /dev/null +++ b/dfvfs/vfs/apfs_container_file_system.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +"""The APFS container file system implementation.""" + +from __future__ import unicode_literals + +import pyfsapfs + +from dfvfs.lib import apfs_container +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.path import apfs_container_path_spec +from dfvfs.resolver import resolver +from dfvfs.vfs import apfs_container_file_entry +from dfvfs.vfs import file_system + + +class APFSContainerFileSystem(file_system.FileSystem): + """APFS container file system using pyfsapfs.""" + + LOCATION_ROOT = '/' + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS_CONTAINER + + def __init__(self, resolver_context): + """Initializes an APFS container file system. + + Args: + resolver_context (resolver.Context): resolver context. + """ + super(APFSContainerFileSystem, self).__init__(resolver_context) + self._file_object = None + self._fsapfs_container = None + + def _Close(self): + """Closes the file system. + + Raises: + IOError: if the close failed. + """ + self._fsapfs_container = None + + self._file_object.close() + self._file_object = None + + def _Open(self, path_spec, mode='rb'): + """Opens the file system defined by path specification. + + Args: + path_spec (PathSpec): a path specification. + mode (Optional[str])): file access mode. The default is 'rb' read-only + binary. + + Raises: + AccessError: if the access to open the file was denied. + IOError: if the file system object could not be opened. + PathSpecError: if the path specification is incorrect. + ValueError: if the path specification is invalid. + """ + if not path_spec.HasParent(): + raise errors.PathSpecError( + 'Unsupported path specification without parent.') + + file_object = resolver.Resolver.OpenFileObject( + path_spec.parent, resolver_context=self._resolver_context) + + try: + fsapfs_container = pyfsapfs.container() + fsapfs_container.open_file_object(file_object) + except: + file_object.close() + raise + + self._file_object = file_object + self._fsapfs_container = fsapfs_container + + def FileEntryExistsByPathSpec(self, path_spec): + """Determines if a file entry for a path specification exists. + + Args: + path_spec (PathSpec): a path specification. + + Returns: + bool: True if the file entry exists. + """ + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex( + path_spec) + + # The virtual root file has not corresponding volume index but + # should have a location. + if volume_index is None: + location = getattr(path_spec, 'location', None) + return location is not None and location == self.LOCATION_ROOT + + return 0 <= volume_index < self._fsapfs_container.number_of_volumes + + def GetAPFSContainer(self): + """Retrieves the APFS container. + + Returns: + pyfsapfs.containter: the APFS container. + """ + return self._fsapfs_container + + def GetAPFSVolumeByPathSpec(self, path_spec): + """Retrieves an APFS volume for a path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + pyfsapfs.volume: an APFS volume or None if not available. + """ + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex( + path_spec) + if volume_index is None: + return None + + return self._fsapfs_container.get_volume(volume_index) + + def GetFileEntryByPathSpec(self, path_spec): + """Retrieves a file entry for a path specification. + + Args: + path_spec (PathSpec): a path specification. + + Returns: + APFSContainerFileEntry: a file entry or None if not exists. + """ + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex( + path_spec) + + # The virtual root file has not corresponding volume index but + # should have a location. + if volume_index is None: + location = getattr(path_spec, 'location', None) + if location is None or location != self.LOCATION_ROOT: + return None + + return apfs_container_file_entry.APFSContainerFileEntry( + self._resolver_context, self, path_spec, is_root=True, + is_virtual=True) + + if (volume_index < 0 or + volume_index >= self._fsapfs_container.number_of_volumes): + return None + + return apfs_container_file_entry.APFSContainerFileEntry( + self._resolver_context, self, path_spec) + + def GetRootFileEntry(self): + """Retrieves the root file entry. + + Returns: + APFSContainerFileEntry: a file entry. + """ + path_spec = apfs_container_path_spec.APFSContainerPathSpec( + location=self.LOCATION_ROOT, parent=self._path_spec.parent) + return self.GetFileEntryByPathSpec(path_spec) diff --git a/dfvfs/vfs/apfs_file_entry.py b/dfvfs/vfs/apfs_file_entry.py new file mode 100644 index 00000000..cf177c2a --- /dev/null +++ b/dfvfs/vfs/apfs_file_entry.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +"""The APFS file entry implementation.""" + +from __future__ import unicode_literals + +from dfdatetime import apfs_time as dfdatetime_apfs_time + +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.path import apfs_path_spec +from dfvfs.vfs import file_entry + + +class APFSDirectory(file_entry.Directory): + """File system directory that uses pyfsapfs.""" + + def _EntriesGenerator(self): + """Retrieves directory entries. + + Since a directory can contain a vast number of entries using + a generator is more memory efficient. + + Yields: + APFSPathSpec: APFS path specification. + """ + try: + fsapfs_file_entry = self._file_system.GetAPFSFileEntryByPathSpec( + self.path_spec) + except errors.PathSpecError: + return + + location = getattr(self.path_spec, 'location', None) + + for fsapfs_sub_file_entry in fsapfs_file_entry.sub_file_entries: + directory_entry = fsapfs_sub_file_entry.name + + if 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 apfs_path_spec.APFSPathSpec( + identifier=fsapfs_sub_file_entry.identifier, location=directory_entry, + parent=self.path_spec.parent) + + +class APFSFileEntry(file_entry.FileEntry): + """File system file entry that uses pyfsapfs.""" + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS + + # Mappings of APFS file types to dfVFS file entry types. + _ENTRY_TYPES = { + 0x1000: definitions.FILE_ENTRY_TYPE_PIPE, + 0x2000: definitions.FILE_ENTRY_TYPE_DEVICE, + 0x4000: definitions.FILE_ENTRY_TYPE_DIRECTORY, + 0x6000: definitions.FILE_ENTRY_TYPE_DEVICE, + 0x8000: definitions.FILE_ENTRY_TYPE_FILE, + 0xa000: definitions.FILE_ENTRY_TYPE_LINK, + 0xc000: definitions.FILE_ENTRY_TYPE_SOCKET, + 0xe000: definitions.FILE_ENTRY_TYPE_WHITEOUT} + + def __init__( + self, resolver_context, file_system, path_spec, fsapfs_file_entry=None, + is_root=False, is_virtual=False): + """Initializes a file entry. + + Args: + resolver_context (Context): resolver context. + file_system (FileSystem): file system. + path_spec (PathSpec): path specification. + fsapfs_file_entry (Optional[pyfsapfs.file_entry]): APFS file entry. + 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. + + Raises: + BackEndError: if the pyfsapfs file entry is missing. + """ + if not fsapfs_file_entry: + fsapfs_file_entry = file_system.GetAPFSFileEntryByPathSpec(path_spec) + if not fsapfs_file_entry: + raise errors.BackEndError('Missing pyfsapfs file entry.') + + super(APFSFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._fsapfs_file_entry = fsapfs_file_entry + + self.entry_type = self._ENTRY_TYPES.get( + fsapfs_file_entry.file_mode & 0xf000, None) + + def _GetDirectory(self): + """Retrieves a directory. + + Returns: + APFSDirectory: directory or None if not available. + """ + if self._fsapfs_file_entry.number_of_sub_file_entries <= 0: + return None + + return APFSDirectory(self._file_system, self.path_spec) + + def _GetLink(self): + """Retrieves the link. + + Returns: + str: path of the linked file. + """ + if self._link is None: + self._link = '' + if self.entry_type != definitions.FILE_ENTRY_TYPE_LINK: + return self._link + + link = self._fsapfs_file_entry.symbolic_link_target + if link and link[0] != self._file_system.PATH_SEPARATOR: + # TODO: make link absolute. + self._link = '/{0:s}'.format(link) + + return self._link + + def _GetStat(self): + """Retrieves information about the file entry. + + Returns: + VFSStat: a stat object. + """ + stat_object = super(APFSFileEntry, self)._GetStat() + + # File data stat information. + stat_object.size = self._fsapfs_file_entry.size + + # Ownership and permissions stat information. + stat_object.mode = self._fsapfs_file_entry.file_mode & 0x0fff + stat_object.uid = self._fsapfs_file_entry.owner_identifier + stat_object.gid = self._fsapfs_file_entry.group_identifier + + # File entry type stat information. + stat_object.type = self.entry_type + + # Other stat information. + stat_object.ino = self._fsapfs_file_entry.identifier + stat_object.fs_type = 'APFS' + + stat_object.is_allocated = True + + return stat_object + + def _GetSubFileEntries(self): + """Retrieves a sub file entries generator. + + Yields: + APFSFileEntry: a sub file entry. + """ + if self._directory is None: + self._directory = self._GetDirectory() + + if self._directory: + for path_spec in self._directory.entries: + yield APFSFileEntry( + self._resolver_context, self._file_system, path_spec) + + @property + def access_time(self): + """dfdatetime.DateTimeValues: access time or None if not available.""" + timestamp = self._fsapfs_file_entry.get_access_time_as_integer() + return dfdatetime_apfs_time.APFSTime(timestamp=timestamp) + + @property + def change_time(self): + """dfdatetime.DateTimeValues: change time or None if not available.""" + timestamp = self._fsapfs_file_entry.get_inode_change_time_as_integer() + return dfdatetime_apfs_time.APFSTime(timestamp=timestamp) + + @property + def creation_time(self): + """dfdatetime.DateTimeValues: creation time or None if not available.""" + timestamp = self._fsapfs_file_entry.get_creation_time_as_integer() + return dfdatetime_apfs_time.APFSTime(timestamp=timestamp) + + @property + def name(self): + """str: name of the file entry, which does not include the full path.""" + # The root directory file name is typically 'root', dfVFS however uses ''. + if self._is_root: + return '' + + return self._fsapfs_file_entry.name + + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + timestamp = self._fsapfs_file_entry.get_modification_time_as_integer() + return dfdatetime_apfs_time.APFSTime(timestamp=timestamp) + + def GetAPFSFileEntry(self): + """Retrieves the APFS file entry. + + Returns: + pyfsapfs.file_entry: APFS file entry. + """ + return self._fsapfs_file_entry + + def GetLinkedFileEntry(self): + """Retrieves the linked file entry, e.g. for a symbolic link. + + Returns: + APFSFileEntry: linked file entry or None if not available. + """ + link = self._GetLink() + if not link: + return None + + # TODO: is there a way to determine the identifier here? + link_identifier = None + + parent_path_spec = getattr(self.path_spec, 'parent', None) + path_spec = apfs_path_spec.APFSPathSpec( + location=link, parent=parent_path_spec) + + is_root = bool( + link == self._file_system.LOCATION_ROOT or + link_identifier == self._file_system.ROOT_DIRECTORY_IDENTIFIER) + + return APFSFileEntry( + self._resolver_context, self._file_system, path_spec, is_root=is_root) + + def GetParentFileEntry(self): + """Retrieves the parent file entry. + + Returns: + APFSFileEntry: parent file entry or None if not available. + """ + location = getattr(self.path_spec, 'location', None) + if location is not None: + parent_location = self._file_system.DirnamePath(location) + if parent_location == '': + parent_location = self._file_system.PATH_SEPARATOR + + parent_identifier = self._fsapfs_file_entry.parent_identifier + if parent_identifier is None: + return None + + parent_path_spec = getattr(self.path_spec, 'parent', None) + path_spec = apfs_path_spec.APFSPathSpec( + location=parent_location, identifier=parent_identifier, + parent=parent_path_spec) + + is_root = bool( + parent_location == self._file_system.LOCATION_ROOT or + parent_identifier == self._file_system.ROOT_DIRECTORY_IDENTIFIER) + + return APFSFileEntry( + self._resolver_context, self._file_system, path_spec, is_root=is_root) diff --git a/dfvfs/vfs/apfs_file_system.py b/dfvfs/vfs/apfs_file_system.py new file mode 100644 index 00000000..8e14965f --- /dev/null +++ b/dfvfs/vfs/apfs_file_system.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +"""The APFS file system implementation.""" + +from __future__ import unicode_literals + +from dfvfs.lib import definitions +from dfvfs.lib import errors +from dfvfs.path import apfs_path_spec +from dfvfs.resolver import resolver +from dfvfs.vfs import file_system +from dfvfs.vfs import apfs_file_entry + + +class APFSFileSystem(file_system.FileSystem): + """File system that uses pyfsapfs.""" + + ROOT_DIRECTORY_IDENTIFIER = 2 + + TYPE_INDICATOR = definitions.TYPE_INDICATOR_APFS + + def __init__(self, resolver_context): + """Initializes an APFS file system. + + Args: + resolver_context (Context): resolver context. + """ + super(APFSFileSystem, self).__init__(resolver_context) + self._fsapfs_volume = None + + def _Close(self): + """Closes the file system. + + Raises: + IOError: if the close failed. + """ + self._fsapfs_volume = None + + def _Open(self, path_spec, mode='rb'): + """Opens the file system defined by path specification. + + Args: + path_spec (PathSpec): path specification. + mode (Optional[str]): file access mode. + + Raises: + AccessError: if the access to open the file was denied. + IOError: if the file system object could not be opened. + OSError: if the file system object could not be opened. + PathSpecError: if the path specification is incorrect. + ValueError: if the path specification is invalid. + """ + if not path_spec.HasParent(): + raise errors.PathSpecError( + 'Unsupported path specification without parent.') + + if path_spec.parent.type_indicator != ( + definitions.TYPE_INDICATOR_APFS_CONTAINER): + raise errors.PathSpecError( + 'Unsupported path specification not type APFS container.') + + apfs_file_system = resolver.Resolver.OpenFileSystem( + path_spec.parent, resolver_context=self._resolver_context) + + fsapfs_volume = apfs_file_system.GetAPFSVolumeByPathSpec(path_spec.parent) + if not fsapfs_volume: + raise IOError('Unable to open APFS volume') + + self._fsapfs_volume = fsapfs_volume + + def FileEntryExistsByPathSpec(self, path_spec): + """Determines if a file entry for a path specification exists. + + Args: + path_spec (PathSpec): path specification. + + Returns: + bool: True if the file entry exists. + + Raises: + BackEndError: if the file entry cannot be opened. + """ + # Opening a file by identifier is faster than opening a file by location. + fsapfs_file_entry = None + location = getattr(path_spec, 'location', None) + identifier = getattr(path_spec, 'identifier', None) + + try: + if identifier is not None: + fsapfs_file_entry = self._fsapfs_volume.get_file_entry_by_identifier( + identifier) + elif location is not None: + fsapfs_file_entry = self._fsapfs_volume.get_file_entry_by_path(location) + + except IOError as exception: + raise errors.BackEndError(exception) + + return fsapfs_file_entry is not None + + def GetFileEntryByPathSpec(self, path_spec): + """Retrieves a file entry for a path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + APFSFileEntry: file entry or None if not available. + + Raises: + BackEndError: if the file entry cannot be opened. + """ + # Opening a file by identifier is faster than opening a file by location. + fsapfs_file_entry = None + location = getattr(path_spec, 'location', None) + identifier = getattr(path_spec, 'identifier', None) + + if (location == self.LOCATION_ROOT or + identifier == self.ROOT_DIRECTORY_IDENTIFIER): + fsapfs_file_entry = self._fsapfs_volume.get_root_directory() + return apfs_file_entry.APFSFileEntry( + self._resolver_context, self, path_spec, + fsapfs_file_entry=fsapfs_file_entry, is_root=True) + + try: + if identifier is not None: + fsapfs_file_entry = self._fsapfs_volume.get_file_entry_by_identifier( + identifier) + elif location is not None: + fsapfs_file_entry = self._fsapfs_volume.get_file_entry_by_path(location) + + except IOError as exception: + raise errors.BackEndError(exception) + + if fsapfs_file_entry is None: + return None + + return apfs_file_entry.APFSFileEntry( + self._resolver_context, self, path_spec, + fsapfs_file_entry=fsapfs_file_entry) + + def GetAPFSFileEntryByPathSpec(self, path_spec): + """Retrieves the APFS file entry for a path specification. + + Args: + path_spec (PathSpec): a path specification. + + Returns: + pyfsapfs.file_entry: file entry. + + Raises: + PathSpecError: if the path specification is missing location and + identifier. + """ + # Opening a file by identifier is faster than opening a file by location. + location = getattr(path_spec, 'location', None) + identifier = getattr(path_spec, 'identifier', None) + + if identifier is not None: + fsapfs_file_entry = self._fsapfs_volume.get_file_entry_by_identifier( + identifier) + elif location is not None: + fsapfs_file_entry = self._fsapfs_volume.get_file_entry_by_path(location) + else: + raise errors.PathSpecError( + 'Path specification missing location and identifier.') + + return fsapfs_file_entry + + def GetRootFileEntry(self): + """Retrieves the root file entry. + + Returns: + APFSFileEntry: file entry. + """ + path_spec = apfs_path_spec.APFSPathSpec( + location=self.LOCATION_ROOT, identifier=self.ROOT_DIRECTORY_IDENTIFIER, + parent=self._path_spec.parent) + return self.GetFileEntryByPathSpec(path_spec) diff --git a/dfvfs/vfs/ntfs_file_system.py b/dfvfs/vfs/ntfs_file_system.py index 8cca6cdf..aacb6ac8 100644 --- a/dfvfs/vfs/ntfs_file_system.py +++ b/dfvfs/vfs/ntfs_file_system.py @@ -62,9 +62,10 @@ def _Open(self, path_spec, mode='rb'): raise errors.PathSpecError( 'Unsupported path specification without parent.') + file_object = resolver.Resolver.OpenFileObject( + path_spec.parent, resolver_context=self._resolver_context) + try: - file_object = resolver.Resolver.OpenFileObject( - path_spec.parent, resolver_context=self._resolver_context) fsnfts_volume = pyfsntfs.volume() fsnfts_volume.open_file_object(file_object) except: diff --git a/requirements.txt b/requirements.txt index bfc306ee..ae7c7c31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ pip >= 7.0.0 PyYAML >= 3.10 backports.lzma ; python_version < '3.0' -dfdatetime >= 20180324 +dfdatetime >= 20181025 dtfabric >= 20170524 libbde-python >= 20140531 libewf-python >= 20131210 +libfsapfs-python >= 20181110 libfsntfs-python >= 20151130 libfvde-python >= 20160719 libfwnt-python >= 20160418 diff --git a/setup.cfg b/setup.cfg index b46dd69f..c0e2755e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ build_requires = python-setuptools requires = PyYAML >= 3.10 libbde-python >= 20140531 libewf-python >= 20131210 + libfsapfs-python2 >= 20181110 libfsntfs-python >= 20151130 libfvde-python >= 20160719 libfwnt-python >= 20160418 @@ -30,7 +31,7 @@ requires = PyYAML >= 3.10 libvslvm-python >= 20160109 python-backports-lzma python-crypto >= 2.6 - python-dfdatetime >= 20180324 + python-dfdatetime >= 20181025 python-dtfabric >= 20170524 python-pysqlite python-pytsk3 >= 20160721 diff --git a/test_data/apfs.dmg b/test_data/apfs.dmg new file mode 100644 index 0000000000000000000000000000000000000000..f16f6f763853cb4536fefccd39266e40891becbe GIT binary patch literal 4194304 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 z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAVA>%w{N#B4MtG_fX~2YTD3UTAXp6h1DhzUh=^d(FxVJw2!g?4Fd0=a_yTF7OYl{IQn+ literal 0 HcmV?d00001 diff --git a/test_data/apfs_encrypted.dmg b/test_data/apfs_encrypted.dmg new file mode 100644 index 0000000000000000000000000000000000000000..7a51a5e52c41bf0225188cf7b9c3b80be5c1800f GIT binary patch literal 4194304 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 Date: Fri, 23 Nov 2018 10:06:24 +0100 Subject: [PATCH 2/3] Changes after review --- dfvfs/lib/apfs_container.py | 2 +- dfvfs/path/apfs_container_path_spec.py | 2 +- dfvfs/path/apfs_path_spec.py | 4 ++-- dfvfs/vfs/apfs_container_file_system.py | 2 +- tests/lib/apfs_container.py | 12 ++++++++++++ tests/vfs/apfs_container_file_entry.py | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dfvfs/lib/apfs_container.py b/dfvfs/lib/apfs_container.py index 1ae19b81..1aef7a41 100644 --- a/dfvfs/lib/apfs_container.py +++ b/dfvfs/lib/apfs_container.py @@ -11,7 +11,7 @@ def APFSContainerPathSpecGetVolumeIndex(path_spec): path_spec (PathSpec): path specification. Returns: - int: volume index. + int: volume index or None if the index cannot be determined. """ volume_index = getattr(path_spec, 'volume_index', None) if volume_index is not None: diff --git a/dfvfs/path/apfs_container_path_spec.py b/dfvfs/path/apfs_container_path_spec.py index 0510d520..d00edc3e 100644 --- a/dfvfs/path/apfs_container_path_spec.py +++ b/dfvfs/path/apfs_container_path_spec.py @@ -22,7 +22,7 @@ def __init__( self, location=None, parent=None, volume_index=None, **kwargs): """Initializes a path specification. - Note that the APFS container path specification must have a parent. + Note that an APFS container path specification must have a parent. Args: location (Optional[str]): location. diff --git a/dfvfs/path/apfs_path_spec.py b/dfvfs/path/apfs_path_spec.py index 2d4093c3..b32cd913 100644 --- a/dfvfs/path/apfs_path_spec.py +++ b/dfvfs/path/apfs_path_spec.py @@ -22,7 +22,7 @@ def __init__( self, identifier=None, location=None, parent=None, **kwargs): """Initializes a path specification. - Note that the APFS path specification must have a parent. + Note that an APFS path specification must have a parent. Args: identifier (Optional[int]): identifier. @@ -30,7 +30,7 @@ def __init__( parent (Optional[PathSpec]): parent path specification. Raises: - ValueError: when identifier and location, or parent are not set. + 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.') diff --git a/dfvfs/vfs/apfs_container_file_system.py b/dfvfs/vfs/apfs_container_file_system.py index 14fc9cfc..15c27616 100644 --- a/dfvfs/vfs/apfs_container_file_system.py +++ b/dfvfs/vfs/apfs_container_file_system.py @@ -96,7 +96,7 @@ def GetAPFSContainer(self): """Retrieves the APFS container. Returns: - pyfsapfs.containter: the APFS container. + pyfsapfs.container: the APFS container. """ return self._fsapfs_container diff --git a/tests/lib/apfs_container.py b/tests/lib/apfs_container.py index 3b3549ee..5ea090ba 100644 --- a/tests/lib/apfs_container.py +++ b/tests/lib/apfs_container.py @@ -49,6 +49,18 @@ def testAPFSContainerPathSpecGetVolumeIndex(self): volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(path_spec) self.assertEqual(volume_index, 1) + path_spec = apfs_container_path_spec.APFSContainerPathSpec( + location='/apfs', parent=self._path_spec) + + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(path_spec) + self.assertIsNone(volume_index) + + path_spec = apfs_container_path_spec.APFSContainerPathSpec( + location='/apfs101', parent=self._path_spec) + + volume_index = apfs_container.APFSContainerPathSpecGetVolumeIndex(path_spec) + self.assertIsNone(volume_index) + if __name__ == '__main__': unittest.main() diff --git a/tests/vfs/apfs_container_file_entry.py b/tests/vfs/apfs_container_file_entry.py index 35693ead..7abc3c4f 100644 --- a/tests/vfs/apfs_container_file_entry.py +++ b/tests/vfs/apfs_container_file_entry.py @@ -41,7 +41,7 @@ def tearDown(self): """Cleans up the needed objects used throughout the test.""" self._file_system.Close() - def testIntialize(self): + def testInitialize(self): """Test the __init__ function.""" file_entry = apfs_container_file_entry.APFSContainerFileEntry( self._resolver_context, self._file_system, From 14f58b8cae4c3c45c32a592f99ac1b1df909b727 Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Fri, 23 Nov 2018 10:42:09 +0100 Subject: [PATCH 3/3] Changes after review --- dfvfs/vfs/apfs_file_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dfvfs/vfs/apfs_file_entry.py b/dfvfs/vfs/apfs_file_entry.py index cf177c2a..ae9fdfda 100644 --- a/dfvfs/vfs/apfs_file_entry.py +++ b/dfvfs/vfs/apfs_file_entry.py @@ -233,6 +233,8 @@ def GetParentFileEntry(self): Returns: APFSFileEntry: parent file entry or None if not available. """ + parent_location = None + location = getattr(self.path_spec, 'location', None) if location is not None: parent_location = self._file_system.DirnamePath(location)