Skip to content

Commit

Permalink
Added LVM support log2timeline#89
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimmetz committed Jan 10, 2016
1 parent 27d4755 commit 5f2d4c1
Show file tree
Hide file tree
Showing 39 changed files with 1,491 additions and 40 deletions.
1 change: 1 addition & 0 deletions dfvfs/analyzer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dfvfs.analyzer import bzip2_analyzer_helper
from dfvfs.analyzer import ewf_analyzer_helper
from dfvfs.analyzer import gzip_analyzer_helper
from dfvfs.analyzer import lvm_analyzer_helper
from dfvfs.analyzer import ntfs_analyzer_helper
from dfvfs.analyzer import qcow_analyzer_helper
from dfvfs.analyzer import tar_analyzer_helper
Expand Down
30 changes: 30 additions & 0 deletions dfvfs/analyzer/lvm_analyzer_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""The Logical Volume Manager (LVM) format analyzer helper implementation."""

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


class LVMAnalyzerHelper(analyzer_helper.AnalyzerHelper):
"""Class that implements the LVM analyzer helper."""

FORMAT_CATEGORIES = frozenset([
definitions.FORMAT_CATEGORY_VOLUME_SYSTEM])

TYPE_INDICATOR = definitions.TYPE_INDICATOR_LVM

def GetFormatSpecification(self):
"""Retrieves the format specification."""
format_specification = specification.FormatSpecification(
self.type_indicator)

# LVM signature.
format_specification.AddNewSignature(b'LABELONE', offset=0)

return format_specification


# Register the analyzer helpers with the analyzer.
analyzer.Analyzer.RegisterHelper(LVMAnalyzerHelper())
3 changes: 2 additions & 1 deletion dfvfs/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
u'pysmraw': 20140612,
u'pyvhdi': 20131210,
u'pyvmdk': 20140421,
u'pyvshadow': 20131209}
u'pyvshadow': 20131209,
u'pyvslvm': 20160109}

# The tuple values are:
# module_name, version_attribute_name, minimum_version, maximum_version
Expand Down
130 changes: 130 additions & 0 deletions dfvfs/file_io/lvm_file_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
"""The Logical Volume Manager (LVM) file-like object implementation."""

import os

from dfvfs.file_io import file_io
from dfvfs.lib import errors
from dfvfs.lib import lvm
from dfvfs.resolver import resolver


class LVMFile(file_io.FileIO):
"""Class that implements a file-like object using pyvslvm."""

def __init__(self, resolver_context):
"""Initializes the file-like object.
Args:
resolver_context: the resolver context (instance of Context).
"""
super(LVMFile, self).__init__(resolver_context)
self._file_system = None
self._vslvm_logical_volume = None

def _Close(self):
"""Closes the file-like object.
Raises:
IOError: if the close failed.
"""
self._vslvm_logical_volume = None

self._file_system.Close()
self._file_system = None

def _Open(self, path_spec=None, mode='rb'):
"""Opens the file-like object defined by path specification.
Args:
path_spec: optional path specification (instance of PathSpec).
mode: optional file access mode. The default is 'rb' read-only binary.
Raises:
AccessError: if the access to open the file was denied.
IOError: if the file-like object could not be opened.
PathSpecError: if the path specification is incorrect.
ValueError: if the path specification is invalid.
"""
if not path_spec:
raise ValueError(u'Missing path specfication.')

volume_index = lvm.LVMPathSpecGetVolumeIndex(path_spec)
if volume_index is None:
raise errors.PathSpecError(
u'Unable to retrieve volume index from path specification.')

self._file_system = resolver.Resolver.OpenFileSystem(
path_spec, resolver_context=self._resolver_context)
vslvm_volume_group = self._file_system.GetLVMVolumeGroup()

if (volume_index < 0 or
volume_index >= vslvm_volume_group.number_of_logical_volumes):
raise errors.PathSpecError((
u'Unable to retrieve LVM logical volume index: {0:d} from path '
u'specification.').format(volume_index))

self._vslvm_logical_volume = vslvm_volume_group.get_logical_volume(
volume_index)

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

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

return self._vslvm_logical_volume.read(size)

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

self._vslvm_logical_volume.seek(offset, whence)

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

return self._vslvm_logical_volume.get_offset()

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

return self._vslvm_logical_volume.size
2 changes: 2 additions & 0 deletions dfvfs/lib/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
TYPE_INDICATOR_EWF = u'EWF'
TYPE_INDICATOR_FAKE = u'FAKE'
TYPE_INDICATOR_GZIP = u'GZIP'
TYPE_INDICATOR_LVM = u'LVM'
TYPE_INDICATOR_MOUNT = u'MOUNT'
TYPE_INDICATOR_NTFS = u'NTFS'
TYPE_INDICATOR_OS = u'OS'
Expand Down Expand Up @@ -49,6 +50,7 @@
TYPE_INDICATOR_VMDK])

VOLUME_SYSTEM_TYPE_INDICATORS = frozenset([
TYPE_INDICATOR_LVM,
TYPE_INDICATOR_TSK_PARTITION,
TYPE_INDICATOR_VSHADOW])

Expand Down
28 changes: 28 additions & 0 deletions dfvfs/lib/lvm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""Helper functions for Logical Volume Manager (LVM) support."""


def LVMPathSpecGetVolumeIndex(path_spec):
"""Retrieves the volume index from the path specification.
Args:
path_spec: the path specification (instance of PathSpec).
"""
volume_index = getattr(path_spec, u'volume_index', None)

if volume_index is None:
location = getattr(path_spec, u'location', None)

if location is None or not location.startswith(u'/lvm'):
return

volume_index = None
try:
volume_index = int(location[4:], 10) - 1
except ValueError:
pass

if volume_index is None or volume_index < 0:
return

return volume_index
1 change: 1 addition & 0 deletions dfvfs/path/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from dfvfs.path import ewf_path_spec
from dfvfs.path import fake_path_spec
from dfvfs.path import gzip_path_spec
from dfvfs.path import lvm_path_spec
from dfvfs.path import mount_path_spec
from dfvfs.path import ntfs_path_spec
from dfvfs.path import os_path_spec
Expand Down
53 changes: 53 additions & 0 deletions dfvfs/path/lvm_path_spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""The Logical Volume Manager (LVM) path specification implementation."""

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


class LVMPathSpec(path_spec.PathSpec):
"""Class that implements the LVM path specification.
Attributes:
location: string containing the location.
volume_index: integer containing the volume index.
"""

TYPE_INDICATOR = definitions.TYPE_INDICATOR_LVM

def __init__(self, location=None, parent=None, volume_index=None, **kwargs):
"""Initializes the path specification object.
Note that the LVM path specification must have a parent.
Args:
location: optional string containing the location.
parent: optional parent path specification (instance of PathSpec).
volume_index: optional integer containing the volume index.
Raises:
ValueError: when parent is not set.
"""
if not parent:
raise ValueError(u'Missing parent value.')

super(LVMPathSpec, self).__init__(parent=parent, **kwargs)
self.location = location
self.volume_index = volume_index

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

if self.location is not None:
string_parts.append(u'location: {0:s}'.format(self.location))
if self.volume_index is not None:
string_parts.append(u'volume index: {0:d}'.format(self.volume_index))

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


# Register the path specification with the factory.
factory.Factory.RegisterPathSpec(LVMPathSpec)
5 changes: 5 additions & 0 deletions dfvfs/resolver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
from dfvfs.resolver import fake_resolver_helper
from dfvfs.resolver import gzip_resolver_helper

try:
from dfvfs.resolver import lvm_resolver_helper
except ImportError:
pass

try:
from dfvfs.resolver import ntfs_resolver_helper
except ImportError:
Expand Down
42 changes: 42 additions & 0 deletions dfvfs/resolver/lvm_resolver_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""The LVM path specification resolver helper implementation."""

# This is necessary to prevent a circular import.
import dfvfs.file_io.lvm_file_io
import dfvfs.vfs.lvm_file_system

from dfvfs.lib import definitions
from dfvfs.resolver import resolver
from dfvfs.resolver import resolver_helper


class LVMResolverHelper(resolver_helper.ResolverHelper):
"""Class that implements the Logical Volume Manager (LVM) resolver helper."""

TYPE_INDICATOR = definitions.TYPE_INDICATOR_LVM

def NewFileObject(self, resolver_context):
"""Creates a new file-like object.
Args:
resolver_context: the resolver context (instance of resolver.Context).
Returns:
The file-like object (instance of file_io.FileIO).
"""
return dfvfs.file_io.lvm_file_io.LVMFile(resolver_context)

def NewFileSystem(self, resolver_context):
"""Creates a new file system object.
Args:
resolver_context: the resolver context (instance of resolver.Context).
Returns:
The file system object (instance of vfs.FileSystem).
"""
return dfvfs.vfs.lvm_file_system.LVMFileSystem(resolver_context)


# Register the resolver helpers with the resolver.
resolver.Resolver.RegisterHelper(LVMResolverHelper())
6 changes: 5 additions & 1 deletion dfvfs/vfs/fake_file_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ def GetFileObject(self, data_stream_name=u''):
return file_object

def GetParentFileEntry(self):
"""Retrieves the parent file entry."""
"""Retrieves the root file entry.
Returns:
The parent file entry (instance of FileEntry) or None.
"""
location = getattr(self.path_spec, u'location', None)
if location is None:
return
Expand Down
8 changes: 6 additions & 2 deletions dfvfs/vfs/file_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,13 @@ def GetLinkedFileEntry(self):
"""Retrieves the linked file entry, e.g. for a symbolic link."""
return

@abc.abstractmethod
def GetParentFileEntry(self):
"""Retrieves the parent file entry."""
"""Retrieves the root file entry.
Returns:
The parent file entry (instance of FileEntry) or None.
"""
return

def GetSubFileEntryByName(self, name, case_sensitive=True):
"""Retrieves a sub file entry by name.
Expand Down
1 change: 1 addition & 0 deletions dfvfs/vfs/file_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
class FileSystem(object):
"""Class that implements the VFS file system object interface."""

LOCATION_ROOT = u'/'
PATH_SEPARATOR = u'/'

def __init__(self, resolver_context):
Expand Down
Loading

0 comments on commit 5f2d4c1

Please sign in to comment.