Skip to content

Commit

Permalink
Added attributes support for fshfs back-end log2timeline#504 (log2tim…
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimmetz authored Jul 23, 2021
1 parent 10f8979 commit 5dc1686
Show file tree
Hide file tree
Showing 18 changed files with 344 additions and 68 deletions.
2 changes: 1 addition & 1 deletion config/dpkg/control
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Homepage: https://github.com/log2timeline/dfvfs

Package: python3-dfvfs
Architecture: all
Depends: libbde-python3 (>= 20140531), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20201107), libfsext-python3 (>= 20210721), libfshfs-python3 (>= 20210530), libfsntfs-python3 (>= 20200921), libfsxfs-python3 (>= 20201114), libfvde-python3 (>= 20160719), libfwnt-python3 (>= 20210717), libluksde-python3 (>= 20200101), libmodi-python3 (>= 20210405), libqcow-python3 (>= 20201213), libsigscan-python3 (>= 20191221), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20210207), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-cffi-backend (>= 1.9.1), python3-cryptography (>= 2.0.2), python3-dfdatetime (>= 20210509), python3-dtfabric (>= 20170524), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-yaml (>= 3.10), ${python3:Depends}, ${misc:Depends}
Depends: libbde-python3 (>= 20140531), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20201107), libfsext-python3 (>= 20210721), libfshfs-python3 (>= 20210722), libfsntfs-python3 (>= 20200921), libfsxfs-python3 (>= 20201114), libfvde-python3 (>= 20160719), libfwnt-python3 (>= 20210717), libluksde-python3 (>= 20200101), libmodi-python3 (>= 20210405), libqcow-python3 (>= 20201213), libsigscan-python3 (>= 20191221), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20210207), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-cffi-backend (>= 1.9.1), python3-cryptography (>= 2.0.2), python3-dfdatetime (>= 20210509), python3-dtfabric (>= 20170524), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), 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
Expand Down
2 changes: 1 addition & 1 deletion dependencies.ini
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ version_property: get_version()
[pyfshfs]
dpkg_name: libfshfs-python3
l2tbinaries_name: libfshfs
minimum_version: 20210530
minimum_version: 20210722
pypi_name: libfshfs-python
rpm_name: libfshfs-python3
version_property: get_version()
Expand Down
107 changes: 107 additions & 0 deletions dfvfs/vfs/hfs_attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""The HFS attribute implementation."""

import os

from dfvfs.lib import errors
from dfvfs.vfs import attribute


class HFSExtendedAttribute(attribute.Attribute):
"""HFS extended attribute that uses pyfshfs."""

def __init__(self, fshfs_extended_attribute):
"""Initializes an attribute.
Args:
fshfs_extended_attribute (pyfshfs.extended_attribute): HFS extended
attribute.
Raises:
BackEndError: if the pyfshfs extended attribute is missing.
"""
if not fshfs_extended_attribute:
raise errors.BackEndError('Missing pyfshfs extended attribute.')

super(HFSExtendedAttribute, self).__init__()
self._fshfs_extended_attribute = fshfs_extended_attribute

@property
def name(self):
"""str: name."""
return self._fshfs_extended_attribute.name

# 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 input/output (IO) object.
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.
"""
return self._fshfs_extended_attribute.read_buffer(size)

def seek(self, offset, whence=os.SEEK_SET):
"""Seeks to an offset within the file input/output (IO) object.
Args:
offset (int): offset to seek.
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.
"""
self._fshfs_extended_attribute.seek_offset(offset, whence)

# get_offset() is preferred above tell() by the libbfio layer used in libyal.
def get_offset(self):
"""Retrieves the current offset into the file input/output (IO) object.
Returns:
int: current offset into the file input/output (IO) object.
Raises:
IOError: if the file input/output (IO)-like object has not been opened.
OSError: if the file input/output (IO)-like object has not been opened.
"""
return self._fshfs_extended_attribute.get_offset()

# Pythonesque alias for get_offset().
def tell(self):
"""Retrieves the current offset into the file input/output (IO) object."""
return self.get_offset()

def get_size(self):
"""Retrieves the size of the file input/output (IO) object.
Returns:
int: size of the file input/output (IO) object.
Raises:
IOError: if the file input/output (IO) object has not been opened.
OSError: if the file input/output (IO) object has not been opened.
"""
return self._fshfs_extended_attribute.get_size()

def seekable(self):
"""Determines if a file input/output (IO) object is seekable.
Returns:
bool: True since a file IO object provides a seek method.
"""
return True
38 changes: 38 additions & 0 deletions dfvfs/vfs/hfs_file_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import hfs_path_spec
from dfvfs.vfs import attribute
from dfvfs.vfs import file_entry
from dfvfs.vfs import hfs_attribute


class HFSDirectory(file_entry.Directory):
Expand Down Expand Up @@ -97,6 +99,24 @@ def __init__(
self.entry_type = self._ENTRY_TYPES.get(
fshfs_file_entry.file_mode & 0xf000, None)

def _GetAttributes(self):
"""Retrieves the attributes.
Returns:
list[Attribute]: attributes.
"""
if self._attributes is None:
stat_attribute = self._GetStatAttribute()
self._attributes = [stat_attribute]

for fshfs_extended_attribute in (
self._fshfs_file_entry.extended_attributes):
extended_attribute = hfs_attribute.HFSExtendedAttribute(
fshfs_extended_attribute)
self._attributes.append(extended_attribute)

return self._attributes

def _GetDirectory(self):
"""Retrieves a directory.
Expand Down Expand Up @@ -149,6 +169,24 @@ def _GetStat(self):

return stat_object

def _GetStatAttribute(self):
"""Retrieves a stat attribute.
Returns:
StatAttribute: a stat attribute.
"""
stat_attribute = attribute.StatAttribute()
stat_attribute.group_identifier = self._fshfs_file_entry.group_identifier
stat_attribute.inode_number = self._fshfs_file_entry.identifier
stat_attribute.mode = self._fshfs_file_entry.file_mode & 0x0fff
# TODO: implement number of hard links support in pyfshfs
# stat_attribute.number_of_links = self._fshfs_file_entry.number_of_links
stat_attribute.owner_identifier = self._fshfs_file_entry.owner_identifier
stat_attribute.size = self._fshfs_file_entry.size
stat_attribute.type = self.entry_type

return stat_attribute

def _GetSubFileEntries(self):
"""Retrieves a sub file entries generator.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ libbde-python >= 20140531
libewf-python >= 20131210
libfsapfs-python >= 20201107
libfsext-python >= 20210721
libfshfs-python >= 20210530
libfshfs-python >= 20210722
libfsntfs-python >= 20200921
libfsxfs-python >= 20201114
libfvde-python >= 20160719
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ requires = libbde-python3 >= 20140531
libewf-python3 >= 20131210
libfsapfs-python3 >= 20201107
libfsext-python3 >= 20210721
libfshfs-python3 >= 20210530
libfshfs-python3 >= 20210722
libfsntfs-python3 >= 20200921
libfsxfs-python3 >= 20201114
libfvde-python3 >= 20160719
Expand Down
Binary file modified test_data/apfs.raw
Binary file not shown.
Binary file modified test_data/hfsplus.raw
Binary file not shown.
Binary file modified test_data/hfsplus.sparseimage
Binary file not shown.
4 changes: 2 additions & 2 deletions tests/file_io/hfs_file_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
class HFSFileTest(shared_test_lib.BaseTestCase):
"""Tests the file-like object implementation using pyfshfs.file_entry."""

_IDENTIFIER_ANOTHER_FILE = 21
_IDENTIFIER_PASSWORDS_TXT = 20
_IDENTIFIER_ANOTHER_FILE = 23
_IDENTIFIER_PASSWORDS_TXT = 22

def setUp(self):
"""Sets up the needed objects used throughout the test."""
Expand Down
4 changes: 2 additions & 2 deletions tests/file_io/test_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ def _TestRead(self, parent_path_spec):
class HFSImageFileTestCase(shared_test_lib.BaseTestCase):
"""Shared functionality for storage media image with a HFS file system."""

_IDENTIFIER_ANOTHER_FILE = 21
_IDENTIFIER_PASSWORDS_TXT = 20
_IDENTIFIER_ANOTHER_FILE = 23
_IDENTIFIER_PASSWORDS_TXT = 22

def setUp(self):
"""Sets up the needed objects used throughout the test."""
Expand Down
20 changes: 10 additions & 10 deletions tests/vfs/apfs_file_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def testEntriesGenerator(self):
class APFSFileEntryTest(shared_test_lib.BaseTestCase):
"""Tests the APFS file entry."""

_IDENTIFIER_A_DIRECTORY = 18
_IDENTIFIER_A_FILE = 19
_IDENTIFIER_A_DIRECTORY = 16
_IDENTIFIER_A_FILE = 17
_IDENTIFIER_A_LINK = 22
_IDENTIFIER_ANOTHER_FILE = 21

Expand Down Expand Up @@ -264,17 +264,17 @@ def testGetStat(self):
self.assertEqual(stat_object.uid, 99)
self.assertEqual(stat_object.gid, 99)

self.assertEqual(stat_object.atime, 1596950905)
self.assertEqual(stat_object.atime_nano, 6246998)
self.assertEqual(stat_object.atime, 1627013324)
self.assertEqual(stat_object.atime_nano, 9960526)

self.assertEqual(stat_object.ctime, 1596950905)
self.assertEqual(stat_object.ctime_nano, 6267234)
self.assertEqual(stat_object.ctime, 1627013324)
self.assertEqual(stat_object.ctime_nano, 9982411)

self.assertEqual(stat_object.crtime, 1596950905)
self.assertEqual(stat_object.crtime_nano, 6267234)
self.assertEqual(stat_object.crtime, 1627013324)
self.assertEqual(stat_object.crtime_nano, 9982411)

self.assertEqual(stat_object.mtime, 1596950905)
self.assertEqual(stat_object.mtime_nano, 6246998)
self.assertEqual(stat_object.mtime, 1627013324)
self.assertEqual(stat_object.mtime_nano, 9960526)

def testIsFunctions(self):
"""Tests the Is? functions."""
Expand Down
11 changes: 5 additions & 6 deletions tests/vfs/ext_file_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ def testModificationTime(self):

def testGetAttributes(self):
"""Tests the _GetAttributes function."""
test_location = '/a_directory/another_file'
test_location = '/a_directory/a_file'
path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_EXT, inode=self._INODE_ANOTHER_FILE,
definitions.TYPE_INDICATOR_EXT, inode=self._INODE_A_FILE,
location=test_location, parent=self._raw_path_spec)
file_entry = self._file_system.GetFileEntryByPathSpec(path_spec)
self.assertIsNotNone(file_entry)
Expand All @@ -114,18 +114,17 @@ def testGetAttributes(self):

file_entry._GetAttributes()
self.assertIsNotNone(file_entry._attributes)
self.assertEqual(len(file_entry._attributes), 2)
self.assertEqual(len(file_entry._attributes), 3)

test_attribute = file_entry._attributes[0]
self.assertIsInstance(test_attribute, attribute.StatAttribute)

test_attribute = file_entry._attributes[1]
self.assertIsInstance(test_attribute, ext_attribute.EXTExtendedAttribute)
self.assertEqual(test_attribute.name, 'security.selinux')
self.assertEqual(test_attribute.name, 'user.myxattr')

test_attribute_value_data = test_attribute.read()
self.assertEqual(
test_attribute_value_data, b'unconfined_u:object_r:unlabeled_t:s0\x00')
self.assertEqual(test_attribute_value_data, b'My extended attribute')

def testGetStat(self):
"""Tests the _GetStat function."""
Expand Down
66 changes: 66 additions & 0 deletions tests/vfs/hfs_attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the HFS attribute."""

import unittest

from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import factory as path_spec_factory
from dfvfs.resolver import context
from dfvfs.vfs import hfs_attribute
from dfvfs.vfs import hfs_file_system

from tests import test_lib as shared_test_lib


class HFSAttributeTest(shared_test_lib.BaseTestCase):
"""Tests the HFS attribute."""

# pylint: disable=protected-access

_IDENTIFIER_A_FILE = 19

def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._resolver_context = context.Context()
test_path = self._GetTestFilePath(['hfsplus.raw'])
self._SkipIfPathNotExists(test_path)

test_os_path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_OS, location=test_path)
self._raw_path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_RAW, parent=test_os_path_spec)
self._hfs_path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_HFS, location='/',
parent=self._raw_path_spec)

self._file_system = hfs_file_system.HFSFileSystem(
self._resolver_context, self._hfs_path_spec)
self._file_system.Open()

def tearDown(self):
"""Cleans up the needed objects used throughout the test."""
self._resolver_context.Empty()

def testIntialize(self):
"""Tests the __init__ function."""
test_location = '/a_directory/a_file'
path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_HFS, identifier=self._IDENTIFIER_A_FILE,
location=test_location, parent=self._raw_path_spec)
file_entry = self._file_system.GetFileEntryByPathSpec(path_spec)

fshfs_attribute = file_entry._fshfs_file_entry.get_extended_attribute(0)
self.assertIsNotNone(fshfs_attribute)
self.assertEqual(fshfs_attribute.name, 'myxattr')

test_attribute = hfs_attribute.HFSExtendedAttribute(fshfs_attribute)
self.assertIsNotNone(test_attribute)

with self.assertRaises(errors.BackEndError):
hfs_attribute.HFSExtendedAttribute(None)


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 5dc1686

Please sign in to comment.