Repository: google/turbinia

Issue:
Improve exception handling in FileSystemTimelineJob
Under certain conditions, dfvfs may throw exceptions that are not currently being handked:

```
dfvfs.lib.errors.BackEndError: Unable to open file system with error: pyfsext_volume_open_file_object: unable to open volume. libfsext_superblock_read_data: unsupported read-only compatible features flags: 0xff000003. libfsext_superblock_read_file_io_handle: unable to read superblock at offset: 1024 (0x00000400). libfsext_internal_volume_read_block_groups: unable to read superblock: 0 at offset: 1024 (0x00000400). libfsext_internal_volume_open_read: unable to read block groups. libfsext_volume_open_file_io_handle: unable to read from file IO handle.
```



Discussion:

Resolving patch:
diff --git a/turbinia/workers/file_system_timeline.py b/turbinia/workers/file_system_timeline.py
--- a/turbinia/workers/file_system_timeline.py
+++ b/turbinia/workers/file_system_timeline.py
@@ -34,6 +34,7 @@
 
 
 class FileSystemTimelineTask(TurbiniaTask):
+  """Task to generate file system timelines. """
 
   REQUIRED_STATES = [state.ATTACHED]
 
@@ -61,31 +62,49 @@ def run(self, evidence, result):
     # Create the FileEntryLister client and generate the path specs
     # for all available partitions.
     entry_lister = file_entry_lister.FileEntryLister()
-    base_path_specs = entry_lister.GetBasePathSpecs(
-        evidence.device_path, options=volume_scanner_options)
+    try:
+      base_path_specs = entry_lister.GetBasePathSpecs(
+          evidence.device_path, options=volume_scanner_options)
+    except dfvfs_errors.ScannerError as exception:
+      status = 'Unable to open evidence: {0!s}'.format(exception)
+      result.close(self, success=False, status=status)
 
     # Iterate over all file entries and generate the output in bodyfile
     # format.
     try:
-      with open(bodyfile_output, 'w') as file_object:
-        for file_entry, path_segments in entry_lister.ListFileEntries(
-            base_path_specs):
-          bodyfile_entries = entry_lister.GetBodyfileEntries(
-              file_entry, path_segments)
-          for bodyfile_entry in bodyfile_entries:
-            file_object.write(bodyfile_entry)
-            file_object.write('\n')
-            number_of_entries += 1
-      output_evidence.number_of_entries = number_of_entries
-      result.add_evidence(output_evidence, evidence.config)
-      status = 'Generated file system timeline containing [{0:d}] entries'.format(
-          number_of_entries)
-      result.close(self, success=True, status=status)
-    except dfvfs_errors.ScannerError as exception:
-      result.log('Error generating bodyfile {0!s}'.format(exception))
-      status = 'Unable to generate bodyfile using provided evidence data.'
+      file_entries = None
+      with open(bodyfile_output, 'w', encoding='utf-8') as file_object:
+        file_entries = enumerate(entry_lister.ListFileEntries(base_path_specs))
+        while file_entries:
+          try:
+            _, (file_entry, path_segments) = next(file_entries)
+            bodyfile_entries = entry_lister.GetBodyfileEntries(
+                file_entry, path_segments)
+            for bodyfile_entry in bodyfile_entries:
+              file_object.write(bodyfile_entry)
+              file_object.write('\n')
+              number_of_entries += 1
+          except StopIteration:
+            break
+          except (dfvfs_errors.AccessError, dfvfs_errors.BackEndError,
+                  dfvfs_errors.MountPointError,
+                  dfvfs_errors.PathSpecError) as exception:
+            status = 'Unable to process file entry: {0!s}'.format(exception)
+            result.log(status)
+
+      if number_of_entries > 0:
+        output_evidence.number_of_entries = number_of_entries
+        result.add_evidence(output_evidence, evidence.config)
+        status = 'Generated file system timeline containing [{0:d}] entries'.format(
+            number_of_entries)
+        result.close(self, success=True, status=status)
+      else:
+        status = 'Unable to process any file entries.'
+        result.close(self, success=False, status=status)
+
+    except IOError as exception:
+      status = 'Unable to create bodyfile local output file: {0!s}'.format(
+          exception)
       result.close(self, success=False, status=status)
-      raise TurbiniaException(
-          'Could not process volume: {0!s}'.format(exception))
 
     return result


Test patch: