diff --git a/config/dpkg/changelog b/config/dpkg/changelog index fc325271..0e75ae8c 100644 --- a/config/dpkg/changelog +++ b/config/dpkg/changelog @@ -2,4 +2,4 @@ dfvfs (20170719-1) unstable; urgency=low * Auto-generated - -- Log2Timeline Wed, 19 Jul 2017 17:37:42 +0200 \ No newline at end of file + -- Log2Timeline Wed, 19 Jul 2017 17:41:35 +0200 \ No newline at end of file diff --git a/dfvfs/vfs/bde_file_entry.py b/dfvfs/vfs/bde_file_entry.py index 25f088be..1b544e97 100644 --- a/dfvfs/vfs/bde_file_entry.py +++ b/dfvfs/vfs/bde_file_entry.py @@ -49,7 +49,6 @@ def _GetStat(self): """ stat_object = super(BDEFileEntry, self)._GetStat() - # File data stat information. stat_object.size = self._bde_volume.get_size() return stat_object diff --git a/dfvfs/vfs/compressed_stream_file_entry.py b/dfvfs/vfs/compressed_stream_file_entry.py index 0259f387..3d8518ba 100644 --- a/dfvfs/vfs/compressed_stream_file_entry.py +++ b/dfvfs/vfs/compressed_stream_file_entry.py @@ -5,46 +5,65 @@ from dfvfs.lib import definitions from dfvfs.lib import errors +from dfvfs.resolver import resolver from dfvfs.vfs import root_only_file_entry from dfvfs.vfs import vfs_stat class CompressedStreamFileEntry(root_only_file_entry.RootOnlyFileEntry): - """Class that implements a compressed stream file entry object.""" + """Compressed stream file entry.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_COMPRESSED_STREAM - def _GetStat(self): - """Retrieves the stat object. + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False): + """Initializes a file entry. - Returns: - The stat object (instance of vfs.VFSStat). + 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 Raises: BackEndError: when the compressed stream is missing. """ - compressed_stream = self.GetFileObject() + compressed_stream = resolver.Resolver.OpenFileObject( + path_spec, resolver_context=resolver_context) if not compressed_stream: raise errors.BackEndError( 'Unable to open compressed stream: {0:s}.'.format( self.path_spec.comparable)) - try: - stat_object = vfs_stat.VFSStat() + super(CompressedStreamFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._compressed_stream = compressed_stream + self._type = definitions.FILE_ENTRY_TYPE_FILE - # File data stat information. - stat_object.size = compressed_stream.get_size() + def __del__(self): + """Cleans up the file entry.""" + # __del__ can be invoked before __init__ has completed. + if hasattr(self, '_compressed_stream'): + self._compressed_stream.close() + self._compressed_stream = None - # Date and time stat information. + super(CompressedStreamFileEntry, self).__del__() - # Ownership and permissions stat information. + def _GetStat(self): + """Retrieves information about the file entry. - # File entry type stat information. - stat_object.type = stat_object.TYPE_FILE + Returns: + VFSStat: a stat object. + """ + stat_object = vfs_stat.VFSStat() - # Other stat information. + if self._compressed_stream: + stat_object.size = self._compressed_stream.get_size() - finally: - compressed_stream.close() + stat_object.type = self._type return stat_object diff --git a/dfvfs/vfs/cpio_file_entry.py b/dfvfs/vfs/cpio_file_entry.py index a9510494..9a29cf81 100644 --- a/dfvfs/vfs/cpio_file_entry.py +++ b/dfvfs/vfs/cpio_file_entry.py @@ -5,15 +5,16 @@ import stat +from dfdatetime import posix_time as dfdatetime_posix_time + from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.path import cpio_path_spec from dfvfs.vfs import file_entry -from dfvfs.vfs import vfs_stat class CPIODirectory(file_entry.Directory): - """Class that implements a directory object using CPIOArchiveFile.""" + """File system directory that uses CPIOArchiveFile.""" def _EntriesGenerator(self): """Retrieves directory entries. @@ -22,7 +23,7 @@ def _EntriesGenerator(self): a generator is more memory efficient. Yields: - A path specification (instance of path.CPIOPathSpec). + CPIOPathSpec: path specification. """ location = getattr(self.path_spec, 'location', None) @@ -50,139 +51,135 @@ def _EntriesGenerator(self): class CPIOFileEntry(file_entry.FileEntry): - """Class that implements a file entry object using CPIOArchiveFile.""" + """File system file entry that uses CPIOArchiveFile.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_CPIO def __init__( - self, resolver_context, file_system, path_spec, is_root=False, - is_virtual=False, cpio_archive_file_entry=None): - """Initializes the file entry object. + self, resolver_context, file_system, path_spec, + cpio_archive_file_entry=None, is_root=False, is_virtual=False): + """Initializes a file entry object. + Args: - resolver_context: the resolver context (instance of resolver.Context). - file_system: the file system object (instance of FileSystem). - path_spec: the path specification (instance of PathSpec). - is_root: optional boolean value to indicate if the file entry is - the root file entry of the corresponding file system. - is_virtual: optional boolean value to indicate if the file entry is - a virtual file entry emulated by the corresponding file - system. - cpio_archive_file_entry: optional CPIO archive file entry object - (instance of cpio.CPIOArchiveFileEntry). + resolver_context (Context): resolver context. + file_system (FileSystem): file system. + path_spec (PathSpec): path specification. + cpio_archive_file_entry (Optional[CPIOArchiveFileEntry]): CPIO archive + 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: when the CPIO archive file entry is missing in + a non-virtual file entry. """ + if not is_virtual and cpio_archive_file_entry is None: + cpio_archive_file_entry = file_system.GetCPIOArchiveFileEntryByPathSpec( + path_spec) + if not is_virtual and cpio_archive_file_entry is None: + raise errors.BackEndError( + 'Missing CPIO archive file entry in non-virtual file entry.') + super(CPIOFileEntry, self).__init__( resolver_context, file_system, path_spec, is_root=is_root, is_virtual=is_virtual) self._cpio_archive_file_entry = cpio_archive_file_entry + # The stat info member st_mode can have multiple types e.g. + # LINK and DIRECTORY in case of a symbolic link to a directory + # dfVFS currently only supports one type so we need to check + # for LINK first. + mode = getattr(cpio_archive_file_entry, 'mode', 0) + if stat.S_ISLNK(mode): + self._type = definitions.FILE_ENTRY_TYPE_LINK + # The root file entry is virtual and should have type directory. + elif is_virtual or stat.S_ISDIR(mode): + self._type = definitions.FILE_ENTRY_TYPE_DIRECTORY + elif stat.S_ISREG(mode): + self._type = definitions.FILE_ENTRY_TYPE_FILE + elif stat.S_ISCHR(mode) or stat.S_ISBLK(mode): + self._type = definitions.FILE_ENTRY_TYPE_DEVICE + elif stat.S_ISFIFO(mode): + self._type = definitions.FILE_ENTRY_TYPE_PIPE + elif stat.S_ISSOCK(mode): + self._type = definitions.FILE_ENTRY_TYPE_SOCKET + def _GetDirectory(self): """Retrieves a directory. Returns: - A directory object (instance of Directory) or None. + CPIODirectory: a directory or None if not available. """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: return CPIODirectory(self._file_system, self.path_spec) - return def _GetLink(self): """Retrieves the link. - Raises: - BackEndError: when the CPIO archive file entry is missing in - a non-virtual file entry. + Returns: + str: full path of the linked file entry. """ if self._link is None: - cpio_archive_file_entry = self.GetCPIOArchiveFileEntry() - if not self._is_virtual and cpio_archive_file_entry is None: - raise errors.BackEndError( - 'Missing CPIO archive file entry in non-virtual file entry.') - self._link = '' - if stat.S_ISLNK(cpio_archive_file_entry.mode): - cpio_archive_file = self._file_system.GetCPIOArchiveFile() - link_data = cpio_archive_file.ReadDataAtOffset( - cpio_archive_file_entry.data_offset, - cpio_archive_file_entry.data_size) - # TODO: should this be ASCII? - self._link = link_data.decode('ascii') + if self._type != definitions.FILE_ENTRY_TYPE_LINK: + return self._link + + cpio_archive_file = self._file_system.GetCPIOArchiveFile() + link_data = cpio_archive_file.ReadDataAtOffset( + self._cpio_archive_file_entry.data_offset, + self._cpio_archive_file_entry.data_size) + + # TODO: should this be ASCII? + self._link = link_data.decode('ascii') return self._link def _GetStat(self): - """Retrieves the stat object. + """Retrieves information about the file entry. Returns: - The stat object (instance of vfs.VFSStat). - - Raises: - BackEndError: when the CPIO archive file entry is missing in - a non-virtual file entry. + VFSStat: a stat object. """ - cpio_archive_file_entry = self.GetCPIOArchiveFileEntry() - if not self._is_virtual and cpio_archive_file_entry is None: - raise errors.BackEndError( - 'Missing CPIO archive file entry in non-virtual file entry.') - - stat_object = vfs_stat.VFSStat() + stat_object = super(CPIOFileEntry, self)._GetStat() # File data stat information. - stat_object.size = getattr(cpio_archive_file_entry, 'data_size', None) - - # Date and time stat information. - stat_object.mtime = getattr( - cpio_archive_file_entry, 'modification_time', None) + stat_object.size = getattr( + self._cpio_archive_file_entry, 'data_size', None) # Ownership and permissions stat information. - mode = getattr(cpio_archive_file_entry, 'mode', 0) + mode = getattr(self._cpio_archive_file_entry, 'mode', 0) stat_object.mode = stat.S_IMODE(mode) stat_object.uid = getattr( - cpio_archive_file_entry, 'user_identifier', None) + self._cpio_archive_file_entry, 'user_identifier', None) stat_object.gid = getattr( - cpio_archive_file_entry, 'group_identifier', None) - - # File entry type stat information. - - # The stat info member st_mode can have multiple types e.g. - # LINK and DIRECTORY in case of a symbolic link to a directory - # dfVFS currently only supports one type so we need to check - # for LINK first. - if stat.S_ISLNK(mode): - stat_object.type = stat_object.TYPE_LINK - # The root file entry is virtual and should have type directory. - elif self._is_virtual or stat.S_ISDIR(mode): - stat_object.type = stat_object.TYPE_DIRECTORY - elif stat.S_ISREG(mode): - stat_object.type = stat_object.TYPE_FILE - elif stat.S_ISCHR(mode) or stat.S_ISBLK(mode): - stat_object.type = stat_object.TYPE_DEVICE - elif stat.S_ISFIFO(mode): - stat_object.type = stat_object.TYPE_PIPE - elif stat.S_ISSOCK(mode): - stat_object.type = stat_object.TYPE_SOCKET + self._cpio_archive_file_entry, 'group_identifier', None) return stat_object @property def name(self): - """The name of the file entry, which does not include the full path.""" - cpio_archive_file_entry = self.GetCPIOArchiveFileEntry() - + """str: name of the file entry, which does not include the full path.""" # Note that the root file entry is virtual and has no # cpio_archive_file_entry. - if cpio_archive_file_entry is None: + if self._cpio_archive_file_entry is None: return '' - return self._file_system.BasenamePath(cpio_archive_file_entry.path) + return self._file_system.BasenamePath(self._cpio_archive_file_entry.path) + + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + timestamp = getattr( + self._cpio_archive_file_entry, 'modification_time', None) + if timestamp is not None: + return dfdatetime_posix_time.PosixTime(timestamp=timestamp) @property def sub_file_entries(self): - """The sub file entries (generator of instance of vfs.FileEntry).""" + """generator(CPIOFileEntry): sub file entries.""" if self._directory is None: self._directory = self._GetDirectory() @@ -195,31 +192,19 @@ def GetCPIOArchiveFileEntry(self): """Retrieves the CPIO archive file entry object. Returns: - The CPIO archive file entry object (instance of - cpio.CPIOArchiveFileEntry). + CPIOArchiveFileEntry: CPIO archive file entry. Raises: - ValueError: if the path specification is incorrect. + PathSpecError: if the path specification is incorrect. """ - if not self._cpio_archive_file_entry: - location = getattr(self.path_spec, 'location', None) - if location is None: - raise ValueError('Path specification missing location.') - - if not location.startswith(self._file_system.LOCATION_ROOT): - raise ValueError('Invalid location in path specification.') - - if len(location) == 1: - return - - cpio_archive_file = self._file_system.GetCPIOArchiveFile() - self._cpio_archive_file_entry = cpio_archive_file.GetFileEntryByPath( - location[1:]) - return self._cpio_archive_file_entry def GetParentFileEntry(self): - """Retrieves the parent file entry.""" + """Retrieves the parent file entry. + + Returns: + CPIOFileEntry: parent file entry or None if not available. + """ location = getattr(self.path_spec, 'location', None) if location is None: return @@ -227,10 +212,18 @@ def GetParentFileEntry(self): parent_location = self._file_system.DirnamePath(location) if parent_location is None: return + if parent_location == '': parent_location = self._file_system.PATH_SEPARATOR + is_root = True + is_virtual = True + else: + is_root = False + is_virtual = False parent_path_spec = getattr(self.path_spec, 'parent', None) path_spec = cpio_path_spec.CPIOPathSpec( location=parent_location, parent=parent_path_spec) - return CPIOFileEntry(self._resolver_context, self._file_system, path_spec) + return CPIOFileEntry( + self._resolver_context, self._file_system, path_spec, + is_root=is_root, is_virtual=is_virtual) diff --git a/dfvfs/vfs/cpio_file_system.py b/dfvfs/vfs/cpio_file_system.py index 8369462c..a08b5eb6 100644 --- a/dfvfs/vfs/cpio_file_system.py +++ b/dfvfs/vfs/cpio_file_system.py @@ -24,8 +24,8 @@ def __init__(self, resolver_context, encoding='utf-8'): """Initializes a file system object. Args: - resolver_context: the resolver context (instance of resolver.Context). - encoding: optional file entry name encoding. + resolver_context (Context): resolver context. + encoding (Optional[str]): file entry name encoding. """ super(CPIOFileSystem, self).__init__(resolver_context) self._cpio_archive_file = None @@ -48,8 +48,8 @@ def _Open(self, path_spec, mode='rb'): """Opens the file system object defined by path specification. Args: - path_spec: a path specification (instance of PathSpec). - mode: optional file access mode. The default is 'rb' read-only binary. + path_spec (PathSpec): path specification. + mode (Optional[str]): file access mode. Raises: AccessError: if the access to open the file was denied. @@ -94,6 +94,38 @@ def FileEntryExistsByPathSpec(self, path_spec): return self._cpio_archive_file.FileEntryExistsByPath(location[1:]) + def GetCPIOArchiveFile(self): + """Retrieves the CPIO archive file object. + + Returns: + The CPIO archvie file object (instance of cpio.CPIOArchiveFile). + """ + return self._cpio_archive_file + + def GetCPIOArchiveFileEntryByPathSpec(self, path_spec): + """Retrieves the CPIO archive file entry for a path specification. + + Args: + path_spec (PathSpec): a path specification. + + Returns: + CPIOArchiveFileEntry: CPIO archive file entry or None if not available. + + Raises: + PathSpecError: if the path specification is incorrect. + """ + location = getattr(path_spec, 'location', None) + if location is None: + raise errors.PathSpecError('Path specification missing location.') + + if not location.startswith(self.LOCATION_ROOT): + raise errors.PathSpecError('Invalid location in path specification.') + + if len(location) == 1: + return + + return self._cpio_archive_file.GetFileEntryByPath(location[1:]) + def GetFileEntryByPathSpec(self, path_spec): """Retrieves a file entry for a path specification. @@ -132,11 +164,3 @@ def GetRootFileEntry(self): path_spec = cpio_path_spec.CPIOPathSpec( location=self.LOCATION_ROOT, parent=self._path_spec.parent) return self.GetFileEntryByPathSpec(path_spec) - - def GetCPIOArchiveFile(self): - """Retrieves the CPIO archive file object. - - Returns: - The CPIO archvie file object (instance of cpio.CPIOArchiveFile). - """ - return self._cpio_archive_file diff --git a/dfvfs/vfs/encoded_stream_file_entry.py b/dfvfs/vfs/encoded_stream_file_entry.py index 3e5d1c11..d8816e81 100644 --- a/dfvfs/vfs/encoded_stream_file_entry.py +++ b/dfvfs/vfs/encoded_stream_file_entry.py @@ -5,6 +5,7 @@ from dfvfs.lib import definitions from dfvfs.lib import errors +from dfvfs.resolver import resolver from dfvfs.vfs import root_only_file_entry from dfvfs.vfs import vfs_stat @@ -14,37 +15,55 @@ class EncodedStreamFileEntry(root_only_file_entry.RootOnlyFileEntry): TYPE_INDICATOR = definitions.TYPE_INDICATOR_ENCODED_STREAM - def _GetStat(self): - """Retrieves the stat object. + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False): + """Initializes a file entry. - Returns: - The stat object (instance of vfs.VFSStat). + 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 Raises: BackEndError: when the encoded stream is missing. """ - encoded_stream = self.GetFileObject() + encoded_stream = resolver.Resolver.OpenFileObject( + path_spec, resolver_context=resolver_context) if not encoded_stream: raise errors.BackEndError( 'Unable to open encoded stream: {0:s}.'.format( self.path_spec.comparable)) - try: - stat_object = vfs_stat.VFSStat() + super(EncodedStreamFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._encoded_stream = encoded_stream + self._type = definitions.FILE_ENTRY_TYPE_FILE - # File data stat information. - stat_object.size = encoded_stream.get_size() + def __del__(self): + """Cleans up the file entry.""" + # __del__ can be invoked before __init__ has completed. + if hasattr(self, '_encoded_stream'): + self._encoded_stream.close() + self._encoded_stream = None - # Date and time stat information. + super(EncodedStreamFileEntry, self).__del__() - # Ownership and permissions stat information. + def _GetStat(self): + """Retrieves information about the file entry. - # File entry type stat information. - stat_object.type = stat_object.TYPE_FILE + Returns: + VFSStat: a stat object. + """ + stat_object = vfs_stat.VFSStat() - # Other stat information. + if self._encoded_stream: + stat_object.size = self._encoded_stream.get_size() - finally: - encoded_stream.close() + stat_object.type = self._type return stat_object diff --git a/dfvfs/vfs/encrypted_stream_file_entry.py b/dfvfs/vfs/encrypted_stream_file_entry.py index d283fe15..daf26e61 100644 --- a/dfvfs/vfs/encrypted_stream_file_entry.py +++ b/dfvfs/vfs/encrypted_stream_file_entry.py @@ -5,46 +5,65 @@ from dfvfs.lib import definitions from dfvfs.lib import errors +from dfvfs.resolver import resolver from dfvfs.vfs import root_only_file_entry from dfvfs.vfs import vfs_stat class EncryptedStreamFileEntry(root_only_file_entry.RootOnlyFileEntry): - """Class that implements a encrypted stream file entry object.""" + """Encrypted stream file entry.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_ENCRYPTED_STREAM - def _GetStat(self): - """Retrieves the stat object. + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False): + """Initializes a file entry. - Returns: - The stat object (instance of vfs.VFSStat). + 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 Raises: BackEndError: when the encrypted stream is missing. """ - encrypted_stream = self.GetFileObject() + encrypted_stream = resolver.Resolver.OpenFileObject( + path_spec, resolver_context=resolver_context) if not encrypted_stream: raise errors.BackEndError( 'Unable to open encrypted stream: {0:s}.'.format( self.path_spec.comparable)) - try: - stat_object = vfs_stat.VFSStat() + super(EncryptedStreamFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._encrypted_stream = encrypted_stream + self._type = definitions.FILE_ENTRY_TYPE_FILE - # File data stat information. - stat_object.size = encrypted_stream.get_size() + def __del__(self): + """Cleans up the file entry.""" + # __del__ can be invoked before __init__ has completed. + if hasattr(self, '_encrypted_stream'): + self._encrypted_stream.close() + self._encrypted_stream = None - # Date and time stat information. + super(EncryptedStreamFileEntry, self).__del__() - # Ownership and permissions stat information. + def _GetStat(self): + """Retrieves information about the file entry. - # File entry type stat information. - stat_object.type = stat_object.TYPE_FILE + Returns: + VFSStat: a stat object. + """ + stat_object = vfs_stat.VFSStat() - # Other stat information. + if self._encrypted_stream: + stat_object.size = self._encrypted_stream.get_size() - finally: - encrypted_stream.close() + stat_object.type = self._type return stat_object diff --git a/dfvfs/vfs/fake_file_entry.py b/dfvfs/vfs/fake_file_entry.py index c9b50145..7e30abca 100644 --- a/dfvfs/vfs/fake_file_entry.py +++ b/dfvfs/vfs/fake_file_entry.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals +from dfdatetime import fake_time as dfdatetime_fake_time + from dfvfs.lib import definitions from dfvfs.file_io import fake_file_io from dfvfs.path import fake_path_spec @@ -10,7 +12,7 @@ class FakeDirectory(file_entry.Directory): - """Class that implements a fake directory object.""" + """Fake file system directory.""" def _EntriesGenerator(self): """Retrieves directory entries. @@ -19,7 +21,7 @@ def _EntriesGenerator(self): a generator is more memory efficient. Yields: - A path specification (instance of path.FakePathSpec). + FakePathSpec: a path specification. """ location = getattr(self.path_spec, 'location', None) if location is None: @@ -44,50 +46,69 @@ def _EntriesGenerator(self): class FakeFileEntry(file_entry.FileEntry): - """Class that implements a fake file entry object.""" + """Fake file system file entry.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_FAKE - def __init__(self, resolver_context, file_system, path_spec, is_root=False): - """Initializes the file entry object. + def __init__( + self, resolver_context, file_system, path_spec, file_entry_type=None, + is_root=False): + """Initializes a file entry. Args: - resolver_context: the resolver context (instance of resolver.Context). - file_system: the file system object (instance of FileSystem). - path_spec: the path specification object (instance of PathSpec). - is_root: optional boolean value to indicate if the file entry is - the root file entry of the corresponding file system. + resolver_context (Context): resolver context. + file_system (FileSystem): file system. + path_spec (PathSpec): path specification. + file_entry_type (Optional[str]): file entry type. + is_root (Optional[bool]): True if the file entry is the root file entry + of the corresponding file system. """ super(FakeFileEntry, self).__init__( resolver_context, file_system, path_spec, is_root=is_root, is_virtual=True) + self._date_time = dfdatetime_fake_time.FakeTime() self._name = None + self._type = file_entry_type def _GetDirectory(self): """Retrieves a directory. Returns: - A directory object (instance of Directory) or None. + FakeDirectoyr: a directory or None if not available. """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: return FakeDirectory(self._file_system, self.path_spec) - return def _GetStat(self): - """Retrieves the stat object (instance of vfs.VFSStat).""" + """Retrieves information about the file entry. + + Returns: + VFSStat: a stat object. + """ + stat_object = super(FakeFileEntry, self)._GetStat() + location = getattr(self.path_spec, 'location', None) - if location is None: - return + if location: + file_data = self._file_system.GetDataByPath(location) - return self._file_system.GetStatObjectByPath(location) + if file_data is not None: + stat_object.size = len(file_data) + + return stat_object + + @property + def access_time(self): + """dfdatetime.DateTimeValues: access time or None if not available.""" + return self._date_time + + @property + def change_time(self): + """dfdatetime.DateTimeValues: change time or None if not available.""" + return self._date_time @property def link(self): - """The full path of the linked file entry.""" + """str: full path of the linked file entry.""" if not self.IsLink(): return '' @@ -97,9 +118,14 @@ def link(self): return self._file_system.GetDataByPath(location) + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + return self._date_time + @property def name(self): - """The name of the file entry, which does not include the full path.""" + """str: name of the file entry, without the full path.""" if self._name is None: location = getattr(self.path_spec, 'location', None) if location is not None: @@ -108,7 +134,7 @@ def name(self): @property def sub_file_entries(self): - """The sub file entries (generator of instance of vfs.FakeFileEntry).""" + """generator[FakeFileEntry]: sub file entries.""" if self._directory is None: self._directory = self._GetDirectory() @@ -121,12 +147,11 @@ def GetFileObject(self, data_stream_name=''): """Retrieves the file-like object. Args: - data_stream_name: optional data stream name. The default is - an empty string which represents the default - data stream. + data_stream_name (Optional[str]): name of the data stream, where an empty + string represents the default data stream. Returns: - A file-like object (instance of file_io.FileIO) or None. + FakeFileIO: a file-like object or None if not available. Raises: IOError: if the file entry is not a file. @@ -150,7 +175,7 @@ def GetParentFileEntry(self): """Retrieves the root file entry. Returns: - The parent file entry (instance of FileEntry) or None. + FakeFileEntry: parent file entry or None if not available. """ location = getattr(self.path_spec, 'location', None) if location is None: diff --git a/dfvfs/vfs/fake_file_system.py b/dfvfs/vfs/fake_file_system.py index 3c35aa67..f9172ad7 100644 --- a/dfvfs/vfs/fake_file_system.py +++ b/dfvfs/vfs/fake_file_system.py @@ -3,18 +3,15 @@ from __future__ import unicode_literals -from dfdatetime import fake_time as dfdatetime_fake_time - from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.path import fake_path_spec from dfvfs.vfs import file_system from dfvfs.vfs import fake_file_entry -from dfvfs.vfs import vfs_stat class FakeFileSystem(file_system.FileSystem): - """Class that implements a fake file system object.""" + """Fake file system.""" LOCATION_ROOT = '/' @@ -24,7 +21,7 @@ def __init__(self, resolver_context): """Initializes a file system object. Args: - resolver_context: the resolver context (instance of resolver.Context). + resolver_context (Context): resolver context. """ super(FakeFileSystem, self).__init__(resolver_context) self._paths = {} @@ -43,7 +40,7 @@ def _Open(self, path_spec, mode='rb'): """Opens the file system object defined by path specification. Args: - path_spec (PathSpec): a path specification. + path_spec (PathSpec): path specification. mode (Optional[str]): file access mode. Raises: @@ -81,31 +78,6 @@ def AddFileEntry( if link_data and file_entry_type != definitions.FILE_ENTRY_TYPE_LINK: raise ValueError('Link data set for non-link file entry type.') - stat_object = vfs_stat.VFSStat() - - # File data stat information. - if file_data is not None: - stat_object.size = len(file_data) - - # Date and time stat information. - date_time_values = dfdatetime_fake_time.FakeTime() - - stat_time, stat_time_nano = date_time_values.CopyToStatTimeTuple() - if stat_time is not None: - stat_object.atime = stat_time - stat_object.atime_nano = stat_time_nano - stat_object.ctime = stat_time - stat_object.ctime_nano = stat_time_nano - stat_object.mtime = stat_time - stat_object.mtime_nano = stat_time_nano - - # Ownership and permissions stat information. - - # File entry type stat information. - stat_object.type = file_entry_type - - # Other stat information. - if file_data: path_data = file_data elif link_data: @@ -113,7 +85,7 @@ def AddFileEntry( else: path_data = None - self._paths[path] = (stat_object, path_data) + self._paths[path] = (file_entry_type, path_data) def FileEntryExistsByPath(self, path): """Determines if a file entry for a path exists. @@ -162,12 +134,14 @@ def GetFileEntryByPath(self, path): if path is None: return - if not self.FileEntryExistsByPath(path): + file_entry_type, _ = self._paths.get(path, (None, None)) + if not file_entry_type: return path_spec = fake_path_spec.FakePathSpec(location=path) return fake_file_entry.FakeFileEntry( - self._resolver_context, self, path_spec) + self._resolver_context, self, path_spec, + file_entry_type=file_entry_type) def GetFileEntryByPathSpec(self, path_spec): """Retrieves a file entry for a path specification. @@ -179,14 +153,7 @@ def GetFileEntryByPathSpec(self, path_spec): FileEntry: a file entry or None if not available. """ location = getattr(path_spec, 'location', None) - if location is None: - return - - if not self.FileEntryExistsByPathSpec(path_spec): - return - - return fake_file_entry.FakeFileEntry( - self._resolver_context, self, path_spec) + return self.GetFileEntryByPath(location) def GetPaths(self): """Retrieves the paths dictionary. @@ -204,15 +171,3 @@ def GetRootFileEntry(self): """ path_spec = fake_path_spec.FakePathSpec(location=self.LOCATION_ROOT) return self.GetFileEntryByPathSpec(path_spec) - - def GetStatObjectByPath(self, path): - """Retrieves the stat object for a path. - - Args: - path (str): path of the file entry. - - Returns: - VFSStat: stat object or None. - """ - stat_object, _ = self._paths.get(path, (None, None)) - return stat_object diff --git a/dfvfs/vfs/file_entry.py b/dfvfs/vfs/file_entry.py index d2d5f8ed..cba904c6 100644 --- a/dfvfs/vfs/file_entry.py +++ b/dfvfs/vfs/file_entry.py @@ -110,7 +110,8 @@ def __init__( def __del__(self): """Cleans up the file entry.""" - if self._file_system: + # __del__ can be invoked before __init__ has completed. + if hasattr(self, '_file_system'): self._file_system.Close() self._file_system = None @@ -176,6 +177,7 @@ def _GetStat(self): stat_time, stat_time_nano = access_time.CopyToStatTimeTuple() if stat_time is not None: stat_object.atime = stat_time + if stat_time_nano is not None: stat_object.atime_nano = stat_time_nano change_time = self.change_time @@ -183,6 +185,7 @@ def _GetStat(self): stat_time, stat_time_nano = change_time.CopyToStatTimeTuple() if stat_time is not None: stat_object.ctime = stat_time + if stat_time_nano is not None: stat_object.ctime_nano = stat_time_nano creation_time = self.creation_time @@ -190,6 +193,7 @@ def _GetStat(self): stat_time, stat_time_nano = creation_time.CopyToStatTimeTuple() if stat_time is not None: stat_object.crtime = stat_time + if stat_time_nano is not None: stat_object.crtime_nano = stat_time_nano modification_time = self.modification_time @@ -197,6 +201,7 @@ def _GetStat(self): stat_time, stat_time_nano = modification_time.CopyToStatTimeTuple() if stat_time is not None: stat_object.mtime = stat_time + if stat_time_nano is not None: stat_object.mtime_nano = stat_time_nano # File entry type stat information. diff --git a/dfvfs/vfs/fvde_file_entry.py b/dfvfs/vfs/fvde_file_entry.py index fa53812a..b1cfb9ca 100644 --- a/dfvfs/vfs/fvde_file_entry.py +++ b/dfvfs/vfs/fvde_file_entry.py @@ -6,39 +6,48 @@ from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.vfs import root_only_file_entry -from dfvfs.vfs import vfs_stat class FVDEFileEntry(root_only_file_entry.RootOnlyFileEntry): - """Class that implements a file entry object using FVDE.""" + """File system file entry that uses pyfvde.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_FVDE - def _GetStat(self): - """Retrieves the stat object. + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False): + """Initializes a file entry. - Returns: - VFSStat: stat object. + 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 FVDE file is missing. + BackEndError: when the FVDE volume is missing. """ - fvde_volume = self._file_system.GetFVDEVolume() + fvde_volume = file_system.GetFVDEVolume() if fvde_volume is None: raise errors.BackEndError('Missing FVDE volume.') - stat_object = vfs_stat.VFSStat() - - # File data stat information. - stat_object.size = fvde_volume.get_size() - - # Date and time stat information. + super(FVDEFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._fvde_volume = fvde_volume + self._type = definitions.FILE_ENTRY_TYPE_FILE - # Ownership and permissions stat information. + def _GetStat(self): + """Retrieves information about the file entry. - # File entry type stat information. - stat_object.type = stat_object.TYPE_FILE + Returns: + VFSStat: a stat object. + """ + stat_object = super(FVDEFileEntry, self)._GetStat() - # Other stat information. + stat_object.size = self._fvde_volume.get_size() return stat_object diff --git a/dfvfs/vfs/gzip_file_entry.py b/dfvfs/vfs/gzip_file_entry.py index b3b47fa6..52144e02 100644 --- a/dfvfs/vfs/gzip_file_entry.py +++ b/dfvfs/vfs/gzip_file_entry.py @@ -3,51 +3,76 @@ from __future__ import unicode_literals +from dfdatetime import posix_time as dfdatetime_posix_time + from dfvfs.lib import definitions from dfvfs.lib import errors +from dfvfs.resolver import resolver from dfvfs.vfs import root_only_file_entry -from dfvfs.vfs import vfs_stat class GzipFileEntry(root_only_file_entry.RootOnlyFileEntry): - """Class that implements a file entry object using gzip.""" + """File system file entry that uses gzip.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_GZIP - def _GetStat(self): - """Retrieves the stat object. + def __init__( + self, resolver_context, file_system, path_spec, is_root=False, + is_virtual=False): + """Initializes a file entry. - Returns: - The stat object (instance of vfs.VFSStat). + 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 Raises: BackEndError: when the gzip file is missing. """ - gzip_file = self.GetFileObject() + gzip_file = resolver.Resolver.OpenFileObject( + path_spec, resolver_context=resolver_context) if not gzip_file: - raise errors.BackEndError( - 'Unable to open gzip file: {0:s}.'.format(self.path_spec.comparable)) + raise errors.BackEndError('Missing gzip file.') - try: - stat_object = vfs_stat.VFSStat() + super(GzipFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) + self._gzip_file = gzip_file + self._type = definitions.FILE_ENTRY_TYPE_FILE - # File data stat information. - stat_object.size = gzip_file.uncompressed_data_size + def __del__(self): + """Cleans up the file entry.""" + # __del__ can be invoked before __init__ has completed. + if hasattr(self, '_gzip_file'): + self._gzip_file.close() + self._gzip_file = None - # Date and time stat information. - stat_object.mtime = gzip_file.modification_time + super(GzipFileEntry, self).__del__() - # Ownership and permissions stat information. + def _GetStat(self): + """Retrieves information about the file entry. - # File entry type stat information. - stat_object.type = stat_object.TYPE_FILE + Returns: + VFSStat: a stat object. + """ + stat_object = super(GzipFileEntry, self)._GetStat() - # Other stat information. - # gzip_file.comment - # gzip_file.operating_system - # gzip_file.original_filename + if self._gzip_file: + stat_object.size = self._gzip_file.uncompressed_data_size - finally: - gzip_file.close() + # Other stat information. + # gzip_file.comment + # gzip_file.operating_system + # gzip_file.original_filename return stat_object + + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + timestamp = getattr(self._gzip_file, 'modification_time', None) + if timestamp is not None: + return dfdatetime_posix_time.PosixTime(timestamp=timestamp) diff --git a/dfvfs/vfs/lvm_file_entry.py b/dfvfs/vfs/lvm_file_entry.py index f900d97a..d7dcc0fd 100644 --- a/dfvfs/vfs/lvm_file_entry.py +++ b/dfvfs/vfs/lvm_file_entry.py @@ -3,27 +3,24 @@ from __future__ import unicode_literals -from dfdatetime import posix_time as dfdatetime_posix_time - from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.lib import lvm from dfvfs.path import lvm_path_spec from dfvfs.vfs import file_entry -from dfvfs.vfs import vfs_stat class LVMDirectory(file_entry.Directory): - """Class that implements a directory object using pyvslvm.""" + """File system directory that uses pyvslvm.""" def _EntriesGenerator(self): """Retrieves directory entries. - Since a directory can contain a vast number of entries using - a generator is more memory efficient. + Since a directory can contain a vast number of entries using + a generator is more memory efficient. Yields: - A path specification (instance of PathSpec). + LVMPathSpec: a path specification. """ # Only the virtual root file has directory entries. volume_index = getattr(self.path_spec, 'volume_index', None) @@ -43,93 +40,71 @@ def _EntriesGenerator(self): class LVMFileEntry(file_entry.FileEntry): - """Class that implements a file entry object using pyvslvm.""" + """File system file entry that uses pyvslvm.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_LVM def __init__( self, resolver_context, file_system, path_spec, is_root=False, - is_virtual=False): - """Initializes the file entry object. + is_virtual=False, vslvm_logical_volume=None): + """Initializes a file entry. Args: - resolver_context: the resolver context (instance of resolver.Context). - file_system: the file system object (instance of FileSystem). - path_spec: the path specification (instance of PathSpec). - is_root: optional boolean value to indicate if the file entry is - the root file entry of the corresponding file system. - is_virtual: optional boolean value to indicate if the file entry is - a virtual file entry emulated by the corresponding file - system. + 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 + vslvm_logical_volume (Optional[pyvslvm.logical_volume]): a LVM logical + volume. """ + if not is_virtual and vslvm_logical_volume is None: + vslvm_logical_volume = file_system.GetLVMLogicalVolumeByPathSpec( + path_spec) + if not is_virtual and vslvm_logical_volume is None: + raise errors.BackEndError( + 'Missing vslvm logical volume in non-virtual file entry.') + super(LVMFileEntry, self).__init__( resolver_context, file_system, path_spec, is_root=is_root, is_virtual=is_virtual) self._name = None + self._vslvm_logical_volume = vslvm_logical_volume + + if self._is_virtual: + self._type = definitions.FILE_ENTRY_TYPE_DIRECTORY + else: + self._type = definitions.FILE_ENTRY_TYPE_FILE def _GetDirectory(self): """Retrieves the directory. Returns: - A directory object (instance of Directory) or None. + LVMDirectory: a directory or None if not available. """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: return LVMDirectory(self._file_system, self.path_spec) - return def _GetStat(self): - """Retrieves the stat object. + """Retrieves information about the file entry. Returns: - The stat object (instance of VFSStat). - - Raises: - BackEndError: when the vslvm logical volume is missing in a non-virtual - file entry. + VFSStat: a stat object. """ - vslvm_logical_volume = self.GetLVMLogicalVolume() - if not self._is_virtual and vslvm_logical_volume is None: - raise errors.BackEndError( - 'Missing vslvm logical volume in non-virtual file entry.') - - stat_object = vfs_stat.VFSStat() - - # File data stat information. - if vslvm_logical_volume is not None: - stat_object.size = vslvm_logical_volume.size + stat_object = super(LVMFileEntry, self)._GetStat() - # Date and time stat information. - if vslvm_logical_volume is not None: - # TODO: implement in pyvslvm - # timestamp = vslvm_logical_volume.get_creation_time_as_integer() - timestamp = None - if timestamp is not None: - date_time_values = dfdatetime_posix_time.PosixTime(timestamp) - - stat_time, stat_time_nano = date_time_values.CopyToStatTimeTuple() - if stat_time is not None: - stat_object.crtime = stat_time - stat_object.crtime_nano = stat_time_nano - - # Ownership and permissions stat information. - - # File entry type stat information. - - # The root file entry is virtual and should have type directory. - if self._is_virtual: - stat_object.type = stat_object.TYPE_DIRECTORY - else: - stat_object.type = stat_object.TYPE_FILE + if self._vslvm_logical_volume is not None: + stat_object.size = self._vslvm_logical_volume.size return stat_object + # TODO: implement creation_time property after implementing + # vslvm_logical_volume.get_creation_time_as_integer() + @property def name(self): - """The name of the file entry, which does not include the full path.""" + """str: name of the file entry, without the full path.""" if self._name is None: location = getattr(self.path_spec, 'location', None) if location is not None: @@ -144,7 +119,7 @@ def name(self): @property def sub_file_entries(self): - """The sub file entries (generator of instance of FileEntry).""" + """generator[LVMFileEntry]: sub file entries.""" if self._directory is None: self._directory = self._GetDirectory() @@ -153,23 +128,18 @@ def sub_file_entries(self): yield LVMFileEntry(self._resolver_context, self._file_system, path_spec) def GetLVMLogicalVolume(self): - """Retrieves the LVM logical volume object. + """Retrieves the LVM logical volume. Returns: - A LVM logical volume object (instance of pyvslvm.logical_volume). + pyvslvm.logical_volume: a LVM logical volume. """ - volume_index = lvm.LVMPathSpecGetVolumeIndex(self.path_spec) - if volume_index is None: - return - - vslvm_volume_group = self._file_system.GetLVMVolumeGroup() - return vslvm_volume_group.get_logical_volume(volume_index) + return self._vslvm_logical_volume def GetParentFileEntry(self): """Retrieves the parent file entry. Returns: - The parent file entry object (instance of FileEntry) or None. + LVMFileEntry: parent file entry or None if not available. """ volume_index = lvm.LVMPathSpecGetVolumeIndex(self.path_spec) if volume_index is None: diff --git a/dfvfs/vfs/lvm_file_system.py b/dfvfs/vfs/lvm_file_system.py index a9667d9d..049b7f02 100644 --- a/dfvfs/vfs/lvm_file_system.py +++ b/dfvfs/vfs/lvm_file_system.py @@ -17,15 +17,15 @@ class LVMFileSystem(file_system.FileSystem): - """Class that implements a file system object using pyvslvm.""" + """File system that uses pyvslvm.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_LVM def __init__(self, resolver_context): - """Initializes a file system object. + """Initializes a file system. Args: - resolver_context: the resolver context (instance of Context). + resolver_context (Context): resolver context. """ super(LVMFileSystem, self).__init__(resolver_context) self._file_object = None @@ -49,8 +49,8 @@ def _Open(self, path_spec, mode='rb'): """Opens the file system object defined by path specification. Args: - path_spec: a path specification (instance of PathSpec). - mode: optional file access mode. The default is 'rb' read-only binary. + path_spec (PathSpec): path specification. + mode (Optional[str]): file access mode. Raises: AccessError: if the access to open the file was denied. @@ -84,10 +84,10 @@ def FileEntryExistsByPathSpec(self, path_spec): """Determines if a file entry for a path specification exists. Args: - path_spec: a path specification (instance of PathSpec). + path_spec (PathSpec): path specification. Returns: - Boolean indicating if the file entry exists. + bool: True if the file entry exists. """ volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec) @@ -104,10 +104,10 @@ def GetFileEntryByPathSpec(self, path_spec): """Retrieves a file entry for a path specification. Args: - path_spec: a path specification (instance of PathSpec). + path_spec (PathSpec): path specification. Returns: - A file entry (instance of FileEntry) or None. + LVMFileEntry: a file entry or None if not available. """ volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec) @@ -127,11 +127,24 @@ def GetFileEntryByPathSpec(self, path_spec): return dfvfs.vfs.lvm_file_entry.LVMFileEntry( self._resolver_context, self, path_spec) + def GetLVMLogicalVolumeByPathSpec(self, path_spec): + """Retrieves a LVM logical volume for a path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + pyvslvm.logical_volume: a LVM logical volume or None if not available. + """ + volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec) + if volume_index is not None: + return self._vslvm_volume_group.get_logical_volume(volume_index) + def GetLVMVolumeGroup(self): - """Retrieves the LVM volume group object. + """Retrieves the LVM volume group. Returns: - The LVM handle object (instance of pyvslvm.volume_group). + pyvslvm.volume_group: a LVM volume group. """ return self._vslvm_volume_group @@ -139,7 +152,7 @@ def GetRootFileEntry(self): """Retrieves the root file entry. Returns: - A file entry (instance of FileEntry). + LVMFileEntry: root file entry or None if not available. """ path_spec = lvm_path_spec.LVMPathSpec( location=self.LOCATION_ROOT, parent=self._path_spec.parent) diff --git a/dfvfs/vfs/ntfs_file_entry.py b/dfvfs/vfs/ntfs_file_entry.py index 528c8d43..30a5b4da 100644 --- a/dfvfs/vfs/ntfs_file_entry.py +++ b/dfvfs/vfs/ntfs_file_entry.py @@ -485,7 +485,7 @@ def GetParentFileEntry(self): """Retrieves the parent file entry. Returns: - NTFSFileEntry: parent file entry or None. + NTFSFileEntry: parent file entry or None if not available. """ location = getattr(self.path_spec, 'location', None) if location is not None: diff --git a/dfvfs/vfs/os_file_entry.py b/dfvfs/vfs/os_file_entry.py index 1e36f800..34253011 100644 --- a/dfvfs/vfs/os_file_entry.py +++ b/dfvfs/vfs/os_file_entry.py @@ -10,16 +10,17 @@ import pysmdev +from dfdatetime import posix_time as dfdatetime_posix_time + from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.lib import py2to3 from dfvfs.path import os_path_spec from dfvfs.vfs import file_entry -from dfvfs.vfs import vfs_stat class OSDirectory(file_entry.Directory): - """Class that implements an operating system directory object.""" + """File system directory that uses os.""" def _EntriesGenerator(self): """Retrieves directory entries. @@ -28,7 +29,7 @@ def _EntriesGenerator(self): a generator is more memory efficient. Yields: - A path specification (instance of path.OSPathSpec). + OSPathSpec: a path specification. Raises: AccessError: if the access to list the directory was denied. @@ -65,86 +66,37 @@ def _EntriesGenerator(self): class OSFileEntry(file_entry.FileEntry): - """Class that implements an operating system file entry object.""" + """File system file entry that uses os.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_OS def __init__(self, resolver_context, file_system, path_spec, is_root=False): - """Initializes the file entry object. + """Initializes a file entry. Args: - resolver_context: the resolver context (instance of resolver.Context). - file_system: the file system object (instance of FileSystem). - path_spec: the path specification object (instance of PathSpec). - is_root: optional boolean value to indicate if the file entry is - the root file entry of the corresponding file system. - """ - super(OSFileEntry, self).__init__( - resolver_context, file_system, path_spec, is_root=is_root, - is_virtual=False) - self._name = None - - def _GetDirectory(self): - """Retrieves a directory. - - Returns: - A directory object (instance of Directory) or None. - """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): - return OSDirectory(self._file_system, self.path_spec) - return - - def _GetLink(self): - """Retrieves the link.""" - if self._link is None: - self._link = '' - - if not self.IsLink(): - return self._link - - location = getattr(self.path_spec, 'location', None) - if location is None: - return self._link - - self._link = os.readlink(location) - self._link = os.path.abspath(self._link) - - return self._link - - def _GetStat(self): - """Retrieves the stat object (instance of vfs.VFSStat). + 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. Raises: BackEndError: If an OSError comes up it is caught and an - BackEndError error is raised instead. - Returns: - Stat object (instance of VFSStat) or None if no location is set. + BackEndError error is raised instead. """ - location = getattr(self.path_spec, 'location', None) - if location is None: - return - - stat_object = vfs_stat.VFSStat() - - is_windows_device = False - stat_info = None + location = getattr(path_spec, 'location', None) # Windows does not support running os.stat on device files so we use # libsmdev to do an initial check. - if platform.system() == 'Windows': + is_windows_device = False + if platform.system() == 'Windows' and location: try: is_windows_device = pysmdev.check_device(location) except IOError: pass - if is_windows_device: - stat_object.type = stat_object.TYPE_DEVICE - - else: + stat_info = None + if not is_windows_device and location: # We are only catching OSError. However on the Windows platform # a WindowsError can be raised as well. We are not catching that since # that error does not exist on non-Windows platforms. @@ -152,58 +104,107 @@ def _GetStat(self): stat_info = os.stat(location) except OSError as exception: raise errors.BackEndError( - 'Unable to retrieve stat object with error: {0:s}'.format( + 'Unable to retrieve stat object with error: {0!s}'.format( exception)) - # File data stat information. - stat_object.size = stat_info.st_size - - # Date and time stat information. - stat_object.atime = stat_info.st_atime - stat_object.ctime = stat_info.st_ctime - stat_object.mtime = stat_info.st_mtime + super(OSFileEntry, self).__init__( + resolver_context, file_system, path_spec, is_root=is_root, + is_virtual=False) + self._is_windows_device = is_windows_device + self._name = None + self._stat_info = stat_info - # Ownership and permissions stat information. - stat_object.mode = stat.S_IMODE(stat_info.st_mode) - stat_object.uid = stat_info.st_uid - stat_object.gid = stat_info.st_gid + if is_windows_device: + self._type = definitions.FILE_ENTRY_TYPE_DEVICE + elif stat_info: # If location contains a trailing segment separator and points to # a symbolic link to a directory stat info will not indicate # the file entry as a symbolic link. The following check ensures # that the LINK type is correctly detected. is_link = os.path.islink(location) - # File entry type stat information. - # The stat info member st_mode can have multiple types e.g. # LINK and DIRECTORY in case of a symbolic link to a directory # dfVFS currently only supports one type so we need to check # for LINK first. if stat.S_ISLNK(stat_info.st_mode) or is_link: - stat_object.type = stat_object.TYPE_LINK + self._type = definitions.FILE_ENTRY_TYPE_LINK elif stat.S_ISREG(stat_info.st_mode): - stat_object.type = stat_object.TYPE_FILE + self._type = definitions.FILE_ENTRY_TYPE_FILE elif stat.S_ISDIR(stat_info.st_mode): - stat_object.type = stat_object.TYPE_DIRECTORY + self._type = definitions.FILE_ENTRY_TYPE_DIRECTORY elif (stat.S_ISCHR(stat_info.st_mode) or stat.S_ISBLK(stat_info.st_mode)): - stat_object.type = stat_object.TYPE_DEVICE + self._type = definitions.FILE_ENTRY_TYPE_DEVICE elif stat.S_ISFIFO(stat_info.st_mode): - stat_object.type = stat_object.TYPE_PIPE + self._type = definitions.FILE_ENTRY_TYPE_PIPE elif stat.S_ISSOCK(stat_info.st_mode): - stat_object.type = stat_object.TYPE_SOCKET + self._type = definitions.FILE_ENTRY_TYPE_SOCKET + + def _GetDirectory(self): + """Retrieves a directory. + + Returns: + OSDirectory: a directory object or None if not available. + """ + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: + return OSDirectory(self._file_system, self.path_spec) + + def _GetLink(self): + """Retrieves the link.""" + if self._link is None: + self._link = '' + + if not self.IsLink(): + return self._link + + location = getattr(self.path_spec, 'location', None) + if location is None: + return self._link + + self._link = os.readlink(location) + self._link = os.path.abspath(self._link) + + return self._link + + def _GetStat(self): + """Retrieves information about the file entry. + + Returns: + VFSStat: a stat object or None if not available. + """ + stat_object = super(OSFileEntry, self)._GetStat() + + if not self._is_windows_device: + # File data stat information. + stat_object.size = self._stat_info.st_size + + # Ownership and permissions stat information. + stat_object.mode = stat.S_IMODE(self._stat_info.st_mode) + stat_object.uid = self._stat_info.st_uid + stat_object.gid = self._stat_info.st_gid # Other stat information. - stat_object.ino = stat_info.st_ino + stat_object.ino = self._stat_info.st_ino # stat_info.st_dev # stat_info.st_nlink return stat_object + @property + def access_time(self): + """dfdatetime.DateTimeValues: access time or None if not available.""" + return dfdatetime_posix_time.PosixTime(timestamp=self._stat_info.st_atime) + + @property + def change_time(self): + """dfdatetime.DateTimeValues: change time or None if not available.""" + return dfdatetime_posix_time.PosixTime(timestamp=self._stat_info.st_ctime) + @property def link(self): - """The full path of the linked file entry.""" + """str: full path of the linked file entry.""" if self._link is None: self._link = '' @@ -220,16 +221,21 @@ def link(self): @property def name(self): - """The name of the file entry, which does not include the full path.""" + """str: name of the file entry, without 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) return self._name + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + return dfdatetime_posix_time.PosixTime(timestamp=self._stat_info.st_mtime) + @property def sub_file_entries(self): - """The sub file entries (generator of instance of vfs.OSFileEntry).""" + """generator[OSFileEntry]: sub file entries.""" if self._directory is None: self._directory = self._GetDirectory() @@ -238,7 +244,11 @@ def sub_file_entries(self): yield OSFileEntry(self._resolver_context, self._file_system, path_spec) def GetLinkedFileEntry(self): - """Retrieves the linked file entry, e.g. for a symbolic link.""" + """Retrieves the linked file entry, for example for a symbolic link. + + Retruns: + OSFileEntry: linked file entry or None if not available. + """ link = self._GetLink() if not link: return @@ -250,7 +260,7 @@ def GetParentFileEntry(self): """Retrieves the parent file entry. Returns: - The parent file entry (instance of FileEntry) or None. + OSFileEntry: parent file entry or None if not available. """ location = getattr(self.path_spec, 'location', None) if location is None: diff --git a/dfvfs/vfs/tar_file_entry.py b/dfvfs/vfs/tar_file_entry.py index 23bf4444..82668aa6 100644 --- a/dfvfs/vfs/tar_file_entry.py +++ b/dfvfs/vfs/tar_file_entry.py @@ -3,16 +3,17 @@ from __future__ import unicode_literals +from dfdatetime import posix_time as dfdatetime_posix_time + from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.lib import py2to3 from dfvfs.path import tar_path_spec from dfvfs.vfs import file_entry -from dfvfs.vfs import vfs_stat class TARDirectory(file_entry.Directory): - """Class that implements a directory using tarfile.""" + """File system directory that uses tarfile.""" def _EntriesGenerator(self): """Retrieves directory entries. @@ -76,7 +77,7 @@ def _EntriesGenerator(self): class TARFileEntry(file_entry.FileEntry): - """Class that implements a file entry using tarfile.""" + """File system file entry that uses tarfile.""" TYPE_INDICATOR = definitions.TYPE_INDICATOR_TAR @@ -94,43 +95,49 @@ def __init__( is_virtual (Optional[bool]): True if the file entry is a virtual file entry emulated by the corresponding file system. tar_info (Optional[tarfile.TARInfo]): TAR info. + + Raises: + BackEndError: when the TAR info is missing in a non-virtual file entry. """ + if not is_virtual and tar_info is None: + tar_info = file_system.GetTARInfoByPathSpec(path_spec) + if not is_virtual and tar_info is None: + raise errors.BackEndError('Missing TAR info in non-virtual file entry.') + super(TARFileEntry, self).__init__( resolver_context, file_system, path_spec, is_root=is_root, is_virtual=is_virtual) self._tar_info = tar_info + if self._is_virtual or self._tar_info.isdir(): + self._type = definitions.FILE_ENTRY_TYPE_DIRECTORY + elif self._tar_info.isfile(): + self._type = definitions.FILE_ENTRY_TYPE_FILE + elif self._tar_info.issym() or self._tar_info.islnk(): + self._type = definitions.FILE_ENTRY_TYPE_LINK + elif self._tar_info.ischr() or self._tar_info.isblk(): + self._type = definitions.FILE_ENTRY_TYPE_DEVICE + elif self._tar_info.isfifo(): + self._type = definitions.FILE_ENTRY_TYPE_PIPE + def _GetDirectory(self): """Retrieves a directory. Returns: - TARDirectory: directory or None. + TARDirectory: a directory or None if not available. """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: return TARDirectory(self._file_system, self.path_spec) - return def _GetLink(self): """Retrieves the link. Returns: str: link. - - Raises: - BackEndError: when the TAR info is missing in a non-virtual file entry. """ if self._link is None: - tar_info = self.GetTARInfo() - if not self._is_virtual and not tar_info: - raise errors.BackEndError( - 'Missing TAR info in non-virtual file entry.') - - if tar_info: - self._link = tar_info.linkname + if self._tar_info: + self._link = self._tar_info.linkname return self._link @@ -139,44 +146,24 @@ def _GetStat(self): Returns: VFSStat: stat object. - - Raises: - BackEndError: when the TAR info is missing in a non-virtual file entry. """ - tar_info = self.GetTARInfo() - if not self._is_virtual and tar_info is None: - raise errors.BackEndError('Missing TAR info in non-virtual file entry.') - - stat_object = vfs_stat.VFSStat() + stat_object = super(TARFileEntry, self)._GetStat() # File data stat information. - stat_object.size = getattr(tar_info, 'size', None) - - # Date and time stat information. - stat_object.mtime = getattr(tar_info, 'mtime', None) + stat_object.size = getattr(self._tar_info, 'size', None) # Ownership and permissions stat information. - stat_object.mode = getattr(tar_info, 'mode', None) - stat_object.uid = getattr(tar_info, 'uid', None) - stat_object.gid = getattr(tar_info, 'gid', None) + stat_object.mode = getattr(self._tar_info, 'mode', None) + stat_object.uid = getattr(self._tar_info, 'uid', None) + stat_object.gid = getattr(self._tar_info, 'gid', None) # TODO: implement support for: - # stat_object.uname = getattr(tar_info, 'uname', None) - # stat_object.gname = getattr(tar_info, 'gname', None) + # stat_object.uname = getattr(self._tar_info, 'uname', None) + # stat_object.gname = getattr(self._tar_info, 'gname', None) # File entry type stat information. # The root file entry is virtual and should have type directory. - if self._is_virtual or tar_info.isdir(): - stat_object.type = stat_object.TYPE_DIRECTORY - elif tar_info.isfile(): - stat_object.type = stat_object.TYPE_FILE - elif tar_info.issym() or tar_info.islnk(): - stat_object.type = stat_object.TYPE_LINK - elif tar_info.ischr() or tar_info.isblk(): - stat_object.type = stat_object.TYPE_DEVICE - elif tar_info.isfifo(): - stat_object.type = stat_object.TYPE_PIPE # TODO: determine if this covers all the types: # REGTYPE, AREGTYPE, LNKTYPE, SYMTYPE, DIRTYPE, FIFOTYPE, CONTTYPE, @@ -198,6 +185,13 @@ def name(self): path = None return self._file_system.BasenamePath(path) + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + timestamp = getattr(self._tar_info, 'mtime', None) + if timestamp is not None: + return dfdatetime_posix_time.PosixTime(timestamp=timestamp) + @property def sub_file_entries(self): """generator(TARFileEntry): sub file entries.""" @@ -234,13 +228,21 @@ def GetParentFileEntry(self): parent_location = self._file_system.DirnamePath(location) if parent_location is None: return + if parent_location == '': parent_location = self._file_system.PATH_SEPARATOR + is_root = True + is_virtual = True + else: + is_root = False + is_virtual = False parent_path_spec = getattr(self.path_spec, 'parent', None) path_spec = tar_path_spec.TARPathSpec( location=parent_location, parent=parent_path_spec) - return TARFileEntry(self._resolver_context, self._file_system, path_spec) + return TARFileEntry( + self._resolver_context, self._file_system, path_spec, is_root=is_root, + is_virtual=is_virtual) def GetTARInfo(self): """Retrieves the TAR info. diff --git a/dfvfs/vfs/tar_file_system.py b/dfvfs/vfs/tar_file_system.py index 0c082d1a..119df283 100644 --- a/dfvfs/vfs/tar_file_system.py +++ b/dfvfs/vfs/tar_file_system.py @@ -158,3 +158,30 @@ def GetTARFile(self): tarfile.TARFile: TAR file. """ return self._tar_file + + def GetTARInfoByPathSpec(self, path_spec): + """Retrieves the TAR info for a path specification. + + Args: + path_spec (PathSpec): a path specification. + + Returns: + tarfile.TARInfo: TAR info or None if it does not exist. + + Raises: + PathSpecError: if the path specification is incorrect. + """ + location = getattr(path_spec, 'location', None) + if location is None: + raise errors.PathSpecError('Path specification missing location.') + + if not location.startswith(self.LOCATION_ROOT): + raise errors.PathSpecError('Invalid location in path specification.') + + if len(location) == 1: + return + + try: + return self._tar_file.getmember(location[1:]) + except KeyError: + pass diff --git a/dfvfs/vfs/tsk_file_entry.py b/dfvfs/vfs/tsk_file_entry.py index 5016619c..54b5cd25 100644 --- a/dfvfs/vfs/tsk_file_entry.py +++ b/dfvfs/vfs/tsk_file_entry.py @@ -7,12 +7,109 @@ import pytsk3 +from dfdatetime import definitions as dfdatetime_definitions +from dfdatetime import interface as dfdatetime_interface + from dfvfs.lib import definitions from dfvfs.lib import errors from dfvfs.path import tsk_path_spec from dfvfs.resolver import resolver from dfvfs.vfs import file_entry -from dfvfs.vfs import vfs_stat + + +class TSKTime(dfdatetime_interface.DateTimeValues): + """SleuthKit timestamp.""" + + def __init__(self, timestamp=None, timestamp_fragment=None): + """Initializes a SleuthKit timestamp. + + Args: + timestamp (Optional[int]): POSIX timestamp. + timestamp_fragment (Optional[int]): POSIX timestamp fragment. + """ + # Sleuthkit 4.2.0 switched from 100 nano seconds precision to + # 1 nano second precision. + if pytsk3.TSK_VERSION_NUM >= 0x040200ff: + precision = dfdatetime_definitions.PRECISION_1_NANOSECOND + else: + precision = dfdatetime_definitions.PRECISION_100_NANOSECONDS + + super(TSKTime, self).__init__() + self.precision = precision + self.timestamp = timestamp + self.timestamp_fragment = timestamp_fragment + + def CopyFromString(self, time_string): + """Copies a POSIX timestamp from a date and time string. + + Args: + time_string (str): date and time value formatted as: + YYYY-MM-DD hh:mm:ss.######[+-]##:## + + Where # are numeric digits ranging from 0 to 9 and the seconds + fraction can be either 3 or 6 digits. The time of day, seconds + fraction and time zone offset are optional. The default time zone + is UTC. + """ + date_time_values = self._CopyDateTimeFromString(time_string) + + year = date_time_values.get('year', 0) + month = date_time_values.get('month', 0) + day_of_month = date_time_values.get('day_of_month', 0) + hours = date_time_values.get('hours', 0) + minutes = date_time_values.get('minutes', 0) + seconds = date_time_values.get('seconds', 0) + microseconds = date_time_values.get('microseconds', 0) + + self.timestamp = self._GetNumberOfSecondsFromElements( + year, month, day_of_month, hours, minutes, seconds) + self.timestamp_fragment = microseconds + + if pytsk3.TSK_VERSION_NUM >= 0x040200ff: + self.timestamp_fragment *= 1000 + else: + self.timestamp_fragment *= 10 + + self.is_local_time = False + + def CopyToStatTimeTuple(self): + """Copies the SleuthKit timestamp to a stat timestamp tuple. + + Returns: + tuple[int, int]: a POSIX timestamp in seconds and the remainder in + 100 nano seconds or (None, None) on error. + """ + if self.timestamp is None: + return None, None + + if (self.timestamp_fragment is not None and + pytsk3.TSK_VERSION_NUM >= 0x040200ff): + timestamp_fragment, _ = divmod(self.timestamp_fragment, 100) + else: + timestamp_fragment = self.timestamp_fragment + + return self.timestamp, timestamp_fragment + + def GetPlasoTimestamp(self): + """Retrieves a timestamp that is compatible with plaso. + + Returns: + int: a POSIX timestamp in microseconds or None on error. + """ + if self.timestamp is None: + return + + timestamp = self.timestamp + if self.timestamp_fragment is not None: + if pytsk3.TSK_VERSION_NUM >= 0x040200ff: + timestamp_fragment, _ = divmod(self.timestamp_fragment, 1000) + else: + timestamp_fragment, _ = divmod(self.timestamp_fragment, 10) + + timestamp *= 1000000 + timestamp += timestamp_fragment + + return timestamp class TSKAttribute(file_entry.Attribute): @@ -175,16 +272,23 @@ class TSKFileEntry(file_entry.FileEntry): TYPE_INDICATOR = definitions.TYPE_INDICATOR_TSK + # pytsk3.TSK_FS_TYPE_ENUM is unhashable, preventing a set + # based lookup, hence lists are used. + _TSK_NO_ATIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_ISO9660] + _TSK_NO_MTIME_FS_TYPES = [pytsk3.TSK_FS_TYPE_ISO9660] + _TSK_NO_CTIME_FS_TYPES = [ pytsk3.TSK_FS_TYPE_FAT12, pytsk3.TSK_FS_TYPE_FAT16, pytsk3.TSK_FS_TYPE_FAT32, pytsk3.TSK_FS_TYPE_ISO9660, pytsk3.TSK_FS_TYPE_EXFAT] + _TSK_NO_CRTIME_FS_TYPES = [ pytsk3.TSK_FS_TYPE_FFS1, pytsk3.TSK_FS_TYPE_FFS1B, pytsk3.TSK_FS_TYPE_FFS2, pytsk3.TSK_FS_TYPE_YAFFS2, pytsk3.TSK_FS_TYPE_EXT2, pytsk3.TSK_FS_TYPE_EXT3] + _TSK_HAS_NANO_FS_TYPES = [ pytsk3.TSK_FS_TYPE_EXT4, pytsk3.TSK_FS_TYPE_NTFS, pytsk3.TSK_FS_TYPE_HFS, pytsk3.TSK_FS_TYPE_FFS2, pytsk3.TSK_FS_TYPE_EXFAT] @@ -204,31 +308,60 @@ def __init__( entry emulated by the corresponding file system. parent_inode (Optional[int]): parent inode number. tsk_file (Optional[pytsk3.File]): TSK file. + + Raises: + BackEndError: if the TSK File .info or .info.meta attribute is missing. """ + if (not tsk_file or not tsk_file.info or not tsk_file.info.meta or + not tsk_file.info.fs_info): + tsk_file = file_system.GetTSKFileByPathSpec(path_spec) + if (not tsk_file or not tsk_file.info or not tsk_file.info.meta or + not tsk_file.info.fs_info): + raise errors.BackEndError( + 'Missing TSK File .info, .info.meta or .info.fs_info') + super(TSKFileEntry, self).__init__( resolver_context, file_system, path_spec, is_root=is_root, is_virtual=is_virtual) + self._file_system_type = tsk_file.info.fs_info.ftype self._name = None self._parent_inode = parent_inode self._tsk_file = tsk_file + # The type is an instance of pytsk3.TSK_FS_META_TYPE_ENUM. + tsk_fs_meta_type = getattr( + tsk_file.info.meta, 'type', pytsk3.TSK_FS_META_TYPE_UNDEF) + + if tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_REG: + self._type = definitions.FILE_ENTRY_TYPE_FILE + elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_DIR: + self._type = definitions.FILE_ENTRY_TYPE_DIRECTORY + elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_LNK: + self._type = definitions.FILE_ENTRY_TYPE_LINK + elif (tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_CHR or + tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_BLK): + self._type = definitions.FILE_ENTRY_TYPE_DEVICE + elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_FIFO: + self._type = definitions.FILE_ENTRY_TYPE_PIPE + elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_SOCK: + self._type = definitions.FILE_ENTRY_TYPE_SOCKET + + # TODO: implement support for: + # pytsk3.TSK_FS_META_TYPE_UNDEF + # pytsk3.TSK_FS_META_TYPE_SHAD + # pytsk3.TSK_FS_META_TYPE_WHT + # pytsk3.TSK_FS_META_TYPE_VIRT + def _GetAttributes(self): """Retrieves the attributes. Returns: list[TSKAttribute]: attributes. - - Raises: - BackEndError: if the TSK File .info or .info.meta attribute is missing. """ if self._attributes is None: - tsk_file = self.GetTSKFile() - if not tsk_file or not tsk_file.info or not tsk_file.info.meta: - raise errors.BackEndError('Missing TSK File .info or .info.meta.') - self._attributes = [] - for tsk_attribute in tsk_file: + for tsk_attribute in self._tsk_file: if getattr(tsk_attribute, 'info', None) is None: continue @@ -244,15 +377,8 @@ def _GetDataStreams(self): Returns: list[TSKDataStream]: data streams. - - Raises: - BackEndError: if the TSK File .info or .info.meta attribute is missing. """ if self._data_streams is None: - tsk_file = self.GetTSKFile() - if not tsk_file or not tsk_file.info or not tsk_file.info.meta: - raise errors.BackEndError('Missing TSK File .info or .info.meta.') - if self._file_system.IsHFS(): known_data_attribute_types = [ pytsk3.TSK_FS_ATTR_TYPE_HFS_DEFAULT, @@ -268,12 +394,12 @@ def _GetDataStreams(self): if not known_data_attribute_types: tsk_fs_meta_type = getattr( - tsk_file.info.meta, 'type', pytsk3.TSK_FS_META_TYPE_UNDEF) + self._tsk_file.info.meta, 'type', pytsk3.TSK_FS_META_TYPE_UNDEF) if tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_REG: self._data_streams.append(TSKDataStream(None)) else: - for tsk_attribute in tsk_file: + for tsk_attribute in self._tsk_file: if getattr(tsk_attribute, 'info', None) is None: continue @@ -289,13 +415,8 @@ def _GetDirectory(self): Returns: TSKDirectory: directory or None. """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: return TSKDirectory(self._file_system, self.path_spec) - return def _GetLink(self): """Retrieves the link. @@ -306,25 +427,12 @@ def _GetLink(self): if self._link is None: self._link = '' - if not self.IsLink(): - return self._link - - tsk_file = self.GetTSKFile() - - # Note that because pytsk3.File does not explicitly defines info - # we need to check if the attribute exists and has a value other - # than None. - if getattr(tsk_file, 'info', None) is None: - return self._link - - # If pytsk3.FS_Info.open() was used file.info has an attribute meta - # (pytsk3.TSK_FS_META) that contains the link. - if getattr(tsk_file.info, 'meta', None) is None: + if self._type != definitions.FILE_ENTRY_TYPE_LINK: return self._link # Note that the SleuthKit does not expose NTFS # IO_REPARSE_TAG_MOUNT_POINT or IO_REPARSE_TAG_SYMLINK as a link. - link = getattr(tsk_file.info.meta, 'link', None) + link = getattr(self._tsk_file.info.meta, 'link', None) if link is None: return self._link @@ -347,97 +455,44 @@ def _GetStat(self): Returns: VFSStat: stat object. - - Raises: - BackEndError: if the TSK File .info or .info.meta attribute is missing. """ - tsk_file = self.GetTSKFile() - if not tsk_file or not tsk_file.info or not tsk_file.info.meta: - raise errors.BackEndError('Missing TSK File .info or .info.meta.') - - stat_object = vfs_stat.VFSStat() + stat_object = super(TSKFileEntry, self)._GetStat() # File data stat information. - stat_object.size = getattr(tsk_file.info.meta, 'size', None) + stat_object.size = getattr(self._tsk_file.info.meta, 'size', None) # Date and time stat information. stat_time, stat_time_nano = self._TSKFileTimeCopyToStatTimeTuple( - tsk_file, 'atime') - if stat_time is not None: - stat_object.atime = stat_time - stat_object.atime_nano = stat_time_nano - - stat_time, stat_time_nano = self._TSKFileTimeCopyToStatTimeTuple( - tsk_file, 'bkup') + self._tsk_file, 'bkup') if stat_time is not None: stat_object.bkup = stat_time stat_object.bkup_nano = stat_time_nano stat_time, stat_time_nano = self._TSKFileTimeCopyToStatTimeTuple( - tsk_file, 'ctime') - if stat_time is not None: - stat_object.ctime = stat_time - stat_object.ctime_nano = stat_time_nano - - stat_time, stat_time_nano = self._TSKFileTimeCopyToStatTimeTuple( - tsk_file, 'crtime') - if stat_time is not None: - stat_object.crtime = stat_time - stat_object.crtime_nano = stat_time_nano - - stat_time, stat_time_nano = self._TSKFileTimeCopyToStatTimeTuple( - tsk_file, 'dtime') + self._tsk_file, 'dtime') if stat_time is not None: stat_object.dtime = stat_time stat_object.dtime_nano = stat_time_nano - stat_time, stat_time_nano = self._TSKFileTimeCopyToStatTimeTuple( - tsk_file, 'mtime') - if stat_time is not None: - stat_object.mtime = stat_time - stat_object.mtime_nano = stat_time_nano - # Ownership and permissions stat information. - mode = getattr(tsk_file.info.meta, 'mode', None) + mode = getattr(self._tsk_file.info.meta, 'mode', None) if mode is not None: # We need to cast mode to an int since it is of type # pytsk3.TSK_FS_META_MODE_ENUM. stat_object.mode = int(mode) - stat_object.uid = getattr(tsk_file.info.meta, 'uid', None) - stat_object.gid = getattr(tsk_file.info.meta, 'gid', None) + stat_object.uid = getattr(self._tsk_file.info.meta, 'uid', None) + stat_object.gid = getattr(self._tsk_file.info.meta, 'gid', None) # File entry type stat information. - # The type is an instance of pytsk3.TSK_FS_META_TYPE_ENUM. - tsk_fs_meta_type = getattr( - tsk_file.info.meta, 'type', pytsk3.TSK_FS_META_TYPE_UNDEF) - - if tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_REG: - stat_object.type = stat_object.TYPE_FILE - elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_DIR: - stat_object.type = stat_object.TYPE_DIRECTORY - elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_LNK: - stat_object.type = stat_object.TYPE_LINK - elif (tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_CHR or - tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_BLK): - stat_object.type = stat_object.TYPE_DEVICE - elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_FIFO: - stat_object.type = stat_object.TYPE_PIPE - elif tsk_fs_meta_type == pytsk3.TSK_FS_META_TYPE_SOCK: - stat_object.type = stat_object.TYPE_SOCKET - # TODO: implement support for: - # pytsk3.TSK_FS_META_TYPE_UNDEF - # pytsk3.TSK_FS_META_TYPE_SHAD - # pytsk3.TSK_FS_META_TYPE_WHT - # pytsk3.TSK_FS_META_TYPE_VIRT # Other stat information. - stat_object.ino = getattr(tsk_file.info.meta, 'addr', None) + stat_object.ino = getattr(self._tsk_file.info.meta, 'addr', None) # stat_object.dev = stat_info.st_dev - # stat_object.nlink = getattr(tsk_file.info.meta, 'nlink', None) + # stat_object.nlink = getattr(self._tsk_file.info.meta, 'nlink', None) # stat_object.fs_type = 'Unknown' - flags = getattr(tsk_file.info.meta, 'flags', 0) + flags = getattr(self._tsk_file.info.meta, 'flags', 0) # The flags are an instance of pytsk3.TSK_FS_META_FLAG_ENUM. if int(flags) & pytsk3.TSK_FS_META_FLAG_ALLOC: @@ -447,6 +502,27 @@ def _GetStat(self): return stat_object + def _GetTimeValue(self, name): + """Retrieves a date and time value. + + Args: + name (str): name of the date and time value, for exmample "atime" or + "mtime". + + Returns: + dfdatetime.DateTimeValues: date and time value or None if not available. + """ + timestamp = getattr(self._tsk_file.info.meta, name, None) + + if self._file_system_type in self._TSK_HAS_NANO_FS_TYPES: + name_fragment = '{0:s}_nano'.format(name) + timestamp_fragment = getattr( + self._tsk_file.info.meta, name_fragment, None) + else: + timestamp_fragment = None + + return TSKTime(timestamp=timestamp, timestamp_fragment=timestamp_fragment) + def _TSKFileTimeCopyToStatTimeTuple(self, tsk_file, time_value): """Copies a SleuthKit file object time value to a stat timestamp tuple. @@ -470,28 +546,9 @@ def _TSKFileTimeCopyToStatTimeTuple(self, tsk_file, time_value): raise errors.BackEndError( 'Missing TSK File .info, .info.meta. or .info.fs_info') - file_system_type = tsk_file.info.fs_info.ftype - # pytsk3.TSK_FS_TYPE_ENUM is unhashable, preventing a dictionary lookup - # approach. - if (time_value == 'atime' and - file_system_type in self._TSK_NO_ATIME_FS_TYPES): - return None, None - - if (time_value == 'ctime' and - file_system_type in self._TSK_NO_CTIME_FS_TYPES): - return None, None - - if (time_value == 'crtime' and - file_system_type in self._TSK_NO_CRTIME_FS_TYPES): - return None, None - - if (time_value == 'mtime' and - file_system_type in self._TSK_NO_MTIME_FS_TYPES): - return None, None - stat_time = getattr(tsk_file.info.meta, time_value, None) stat_time_nano = None - if file_system_type in self._TSK_HAS_NANO_FS_TYPES: + if self._file_system_type in self._TSK_HAS_NANO_FS_TYPES: time_value_nano = '{0:s}_nano'.format(time_value) stat_time_nano = getattr(tsk_file.info.meta, time_value_nano, None) @@ -502,6 +559,30 @@ def _TSKFileTimeCopyToStatTimeTuple(self, tsk_file, time_value): return stat_time, stat_time_nano + @property + def access_time(self): + """dfdatetime.DateTimeValues: access time or None if not available.""" + if self._file_system_type in self._TSK_NO_ATIME_FS_TYPES: + return + + return self._GetTimeValue('atime') + + @property + def change_time(self): + """dfdatetime.DateTimeValues: change time or None if not available.""" + if self._file_system_type in self._TSK_NO_CTIME_FS_TYPES: + return + + return self._GetTimeValue('ctime') + + @property + def creation_time(self): + """dfdatetime.DateTimeValues: creation time or None if not available.""" + if self._file_system_type in self._TSK_NO_CRTIME_FS_TYPES: + return + + return self._GetTimeValue('crtime') + @property def name(self): """str: name of the file entry, which does not include the full path. @@ -510,19 +591,11 @@ def name(self): BackEndError: if pytsk3 returns a non UTF-8 formatted name. """ if self._name is None: - tsk_file = self.GetTSKFile() - - # Note that because pytsk3.File does not explicitly defines info - # we need to check if the attribute exists and has a value other - # than None. - if getattr(tsk_file, 'info', None) is None: - return - # If pytsk3.FS_Info.open() was used file.info has an attribute name # (pytsk3.TSK_FS_FILE) that contains the name string. Otherwise the # name from the path specification is used. - if getattr(tsk_file.info, 'name', None) is not None: - name = getattr(tsk_file.info.name, 'name', None) + if getattr(self._tsk_file.info, 'name', None) is not None: + name = getattr(self._tsk_file.info.name, 'name', None) try: # pytsk3 returns an UTF-8 encoded byte string. @@ -538,6 +611,14 @@ def name(self): return self._name + @property + def modification_time(self): + """dfdatetime.DateTimeValues: modification time or None if not available.""" + if self._file_system_type in self._TSK_NO_MTIME_FS_TYPES: + return + + return self._GetTimeValue('mtime') + @property def sub_file_entries(self): """generator(TSKFileEntry): sub file entries.""" @@ -637,19 +718,4 @@ def GetTSKFile(self): Raises: PathSpecError: if the path specification is missing inode and location. """ - if not self._tsk_file: - # Opening a file by inode number is faster than opening a file - # by location. - inode = getattr(self.path_spec, 'inode', None) - location = getattr(self.path_spec, 'location', None) - - fs_info = self._file_system.GetFsInfo() - if inode is not None: - self._tsk_file = fs_info.open_meta(inode=inode) - elif location is not None: - self._tsk_file = fs_info.open(location) - else: - raise errors.PathSpecError( - 'Path specification missing inode and location.') - return self._tsk_file diff --git a/dfvfs/vfs/tsk_file_system.py b/dfvfs/vfs/tsk_file_system.py index 314c3b58..577d0aae 100644 --- a/dfvfs/vfs/tsk_file_system.py +++ b/dfvfs/vfs/tsk_file_system.py @@ -17,17 +17,17 @@ class TSKFileSystem(file_system.FileSystem): - """Class that implements a file system object using pytsk3.""" + """File system that uses pytsk3.""" LOCATION_ROOT = '/' TYPE_INDICATOR = definitions.TYPE_INDICATOR_TSK def __init__(self, resolver_context): - """Initializes a file system object. + """Initializes a file system. Args: - resolver_context: the resolver context (instance of resolver.Context). + resolver_context (Context): resolver context. """ super(TSKFileSystem, self).__init__(resolver_context) self._file_object = None @@ -49,8 +49,8 @@ def _Open(self, path_spec, mode='rb'): """Opens the file system object defined by path specification. Args: - path_spec: a path specification (instance of PathSpec). - mode: optional file access mode. The default is 'rb' read-only binary. + path_spec (PathSpec): path specification. + mode (Optional[str]): file access mode. Raises: AccessError: if the access to open the file was denied. @@ -75,19 +75,6 @@ def _Open(self, path_spec, mode='rb'): self._file_object = file_object self._tsk_file_system = tsk_file_system - def GetRootInode(self): - """Retrieves the root inode or None.""" - # Note that because pytsk3.FS_Info does not explicitly define info - # we need to check if the attribute exists and has a value other - # than None - if getattr(self._tsk_file_system, 'info', None) is None: - return - - # Note that because pytsk3.TSK_FS_INFO does not explicitly define - # root_inum we need to check if the attribute exists and has a value - # other than None - return getattr(self._tsk_file_system.info, 'root_inum', None) - def FileEntryExistsByPathSpec(self, path_spec): """Determines if a file entry for a path specification exists. @@ -95,7 +82,7 @@ def FileEntryExistsByPathSpec(self, path_spec): path_spec: a path specification (instance of PathSpec). Returns: - Boolean indicating if the file entry exists. + bool: True if the file entry exists. """ # Opening a file by inode number is faster than opening a file by location. tsk_file = None @@ -117,10 +104,10 @@ def GetFileEntryByPathSpec(self, path_spec): """Retrieves a file entry for a path specification. Args: - path_spec: a path specification (instance of PathSpec). + path_spec (PathSpec): path specification. Returns: - A file entry (instance of vfs.FileEntry) or None. + TSKFileEntry: a file entry or None if not available. """ # Opening a file by inode number is faster than opening a file by location. tsk_file = None @@ -152,10 +139,10 @@ def GetFileEntryByPathSpec(self, path_spec): self._resolver_context, self, path_spec, tsk_file=tsk_file) def GetFsInfo(self): - """Retrieves the file system info object. + """Retrieves the file system info. Returns: - The SleuthKit file system info object (instance of pytsk3.FS_Info). + pytsk3.FS_Info: file system info. """ return self._tsk_file_system @@ -163,7 +150,7 @@ def GetFsType(self): """Retrieves the file system type. Returns: - The SleuthKit file system type (instance of pytsk3.TSK_FS_TYPE_ENUM). + pytsk3.TSK_FS_TYPE_ENUM: file system type. """ if self._tsk_fs_type is None: self._tsk_fs_type = pytsk3.TSK_FS_TYPE_UNSUPP @@ -180,7 +167,7 @@ def GetRootFileEntry(self): """Retrieves the root file entry. Returns: - A file entry (instance of vfs.FileEntry). + TSKFileEntry: a file entry. """ kwargs = {} @@ -194,11 +181,55 @@ def GetRootFileEntry(self): path_spec = tsk_path_spec.TSKPathSpec(**kwargs) return self.GetFileEntryByPathSpec(path_spec) + def GetRootInode(self): + """Retrieves the root inode. + + Returns: + int: inode number or None if not available. + """ + # Note that because pytsk3.FS_Info does not explicitly define info + # we need to check if the attribute exists and has a value other + # than None + if getattr(self._tsk_file_system, 'info', None) is None: + return + + # Note that because pytsk3.TSK_FS_INFO does not explicitly define + # root_inum we need to check if the attribute exists and has a value + # other than None + return getattr(self._tsk_file_system.info, 'root_inum', None) + + def GetTSKFileByPathSpec(self, path_spec): + """Retrieves the SleuthKit file object for a path specification. + + Args: + path_spec (PathSpec): path specification. + + Returns: + pytsk3.File: TSK file. + + Raises: + PathSpecError: if the path specification is missing inode and location. + """ + # Opening a file by inode number is faster than opening a file + # by location. + inode = getattr(path_spec, 'inode', None) + location = getattr(path_spec, 'location', None) + + if inode is not None: + tsk_file = self._tsk_file_system.open_meta(inode=inode) + elif location is not None: + tsk_file = self._tsk_file_system.open(location) + else: + raise errors.PathSpecError( + 'Path specification missing inode and location.') + + return tsk_file + def IsHFS(self): """Determines if the file system is HFS, HFS+ or HFSX. Returns: - A boolean value indicating the file system is HFS. + bool: True if the file system is HFS. """ tsk_fs_type = self.GetFsType() return tsk_fs_type in [ @@ -208,7 +239,7 @@ def IsNTFS(self): """Determines if the file system is NTFS. Returns: - A boolean value indicating the file system is NTFS. + bool: True if the file system is NTFS. """ tsk_fs_type = self.GetFsType() return tsk_fs_type in [ diff --git a/dfvfs/vfs/tsk_partition_file_entry.py b/dfvfs/vfs/tsk_partition_file_entry.py index 3003c777..95b9064b 100644 --- a/dfvfs/vfs/tsk_partition_file_entry.py +++ b/dfvfs/vfs/tsk_partition_file_entry.py @@ -20,7 +20,7 @@ def _EntriesGenerator(self): a generator is more memory efficient. Yields: - A path specification (instance of path.TSKPartitionPathSpec). + TSKPartitionPathSpec: a path specification. """ # Only the virtual root file has directory entries. part_index = getattr(self.path_spec, 'part_index', None) diff --git a/dfvfs/vfs/vshadow_file_entry.py b/dfvfs/vfs/vshadow_file_entry.py index 2c4041a5..0b976508 100644 --- a/dfvfs/vfs/vshadow_file_entry.py +++ b/dfvfs/vfs/vshadow_file_entry.py @@ -86,13 +86,8 @@ def _GetDirectory(self): Returns: VShadowDirectory: a directory None if not available. """ - if self._stat_object is None: - self._stat_object = self._GetStat() - - if (self._stat_object and - self._stat_object.type == self._stat_object.TYPE_DIRECTORY): + if self._type == definitions.FILE_ENTRY_TYPE_DIRECTORY: return VShadowDirectory(self._file_system, self.path_spec) - return def _GetStat(self): """Retrieves information about the file entry. diff --git a/dfvfs/vfs/zip_file_system.py b/dfvfs/vfs/zip_file_system.py index 4d893610..e7abbb77 100644 --- a/dfvfs/vfs/zip_file_system.py +++ b/dfvfs/vfs/zip_file_system.py @@ -158,7 +158,7 @@ def GetZipFile(self): return self._zip_file def GetZipInfoByPathSpec(self, path_spec): - """Retrieves the ZIP info object for a path specification. + """Retrieves the ZIP info for a path specification. Args: path_spec (PathSpec): a path specification. diff --git a/tests/vfs/cpio_file_system.py b/tests/vfs/cpio_file_system.py index f41fde27..c6266ce2 100644 --- a/tests/vfs/cpio_file_system.py +++ b/tests/vfs/cpio_file_system.py @@ -86,6 +86,8 @@ def testGetRootFileEntry(self): file_system.Close() + # TODO: add tests for GetCPIOArchiveFileEntryByPathSpec function. + if __name__ == '__main__': unittest.main() diff --git a/tests/vfs/lvm_file_entry.py b/tests/vfs/lvm_file_entry.py index 56ec1714..6226c561 100644 --- a/tests/vfs/lvm_file_entry.py +++ b/tests/vfs/lvm_file_entry.py @@ -79,7 +79,8 @@ def tearDown(self): def testIntialize(self): """Test the __init__ function.""" file_entry = lvm_file_entry.LVMFileEntry( - self._resolver_context, self._file_system, self._lvm_path_spec) + self._resolver_context, self._file_system, self._lvm_path_spec, + is_virtual=True) self.assertIsNotNone(file_entry) diff --git a/tests/vfs/tsk_file_entry.py b/tests/vfs/tsk_file_entry.py index 741212fd..8f8c59b5 100644 --- a/tests/vfs/tsk_file_entry.py +++ b/tests/vfs/tsk_file_entry.py @@ -94,16 +94,17 @@ def testGetStat(self): self.assertEqual(stat_object.gid, 5000) self.assertEqual(stat_object.atime, 1337961563) - self.assertEqual(stat_object.atime_nano, None) + self.assertFalse(hasattr(stat_object, u'atime_nano')) + self.assertEqual(stat_object.ctime, 1337961563) - self.assertEqual(stat_object.ctime_nano, None) + self.assertFalse(hasattr(stat_object, u'ctime_nano')) + # EXT2 has no crtime timestamp. - with self.assertRaises(AttributeError): - _ = stat_object.crtime - with self.assertRaises(AttributeError): - _ = stat_object.crtime_nano + self.assertFalse(hasattr(stat_object, u'crtime')) + self.assertFalse(hasattr(stat_object, u'crtime_nano')) + self.assertEqual(stat_object.mtime, 1337961563) - self.assertEqual(stat_object.mtime_nano, None) + self.assertFalse(hasattr(stat_object, u'mtime_nano')) def testIsFunctions(self): """Test the Is? functions."""