Repository: google/turbinia (language: python)

Issue:
Always save stdout/stderr from Task execution
This way we can always get access to the stdout/stderr easily from saved output, and it's not dependent on how logs are saved from the Worker.  This also makes it easier for saving stdout as directly as Evidence output.

Also unset input evidence when serializing `TurbiniaTaskResults` which fixes: #779.

This should be submitted before #770 


Discussion / hints:

Fix patch:
diff --git a/turbinia/client.py b/turbinia/client.py
index 6ca8e6ce6..705bf29c9 100644
--- a/turbinia/client.py
+++ b/turbinia/client.py
@@ -49,6 +49,7 @@
 from turbinia.workers.finalize_request import FinalizeRequestTask
 from turbinia.workers.docker import DockerContainersEnumerationTask
 from turbinia.workers.grep import GrepTask
+from turbinia.workers.fsstat import FsstatTask
 from turbinia.workers.hadoop import HadoopAnalysisTask
 from turbinia.workers.hindsight import HindsightTask
 from turbinia.workers.partitions import PartitionEnumerationTask
diff --git a/turbinia/config/turbinia_config_tmpl.py b/turbinia/config/turbinia_config_tmpl.py
index 871474e27..9d0a003d6 100644
--- a/turbinia/config/turbinia_config_tmpl.py
+++ b/turbinia/config/turbinia_config_tmpl.py
@@ -108,6 +108,10 @@
     'job': 'BulkExtractorJob',
     'programs': ['bulk_extractor'],
     'docker_image': None
+}, {
+    'job': 'FsstatJob',
+    'programs': ['fsstat'],
+    'docker_image': None
 }, {
     'job': 'GrepJob',
     'programs': ['grep'],
diff --git a/turbinia/jobs/__init__.py b/turbinia/jobs/__init__.py
index 569db4a25..759e2ec8a 100644
--- a/turbinia/jobs/__init__.py
+++ b/turbinia/jobs/__init__.py
@@ -17,6 +17,7 @@
 from turbinia.jobs import bulk_extractor
 from turbinia.jobs import docker
 from turbinia.jobs import finalize_request
+from turbinia.jobs import fsstat
 from turbinia.jobs import grep
 from turbinia.jobs import hadoop
 from turbinia.jobs import hindsight
diff --git a/turbinia/jobs/fsstat.py b/turbinia/jobs/fsstat.py
new file mode 100644
index 000000000..a13e696b7
--- /dev/null
+++ b/turbinia/jobs/fsstat.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Job to run fsstat Task."""
+from __future__ import unicode_literals
+from turbinia.evidence import DiskPartition
+from turbinia.jobs import interface
+from turbinia.jobs import manager
+from turbinia.evidence import ReportText
+from turbinia.workers.fsstat import FsstatTask
+
+
+class FsstatJob(interface.TurbiniaJob):
+
+  evidence_input = [DiskPartition]
+  evidence_output = [ReportText]
+
+  NAME = 'FsstatJob'
+
+  def create_tasks(self, evidence):
+    """Create task for Fsstat.
+
+    Args:
+      evidence: List of evidence objects to process
+
+    Returns:
+        A list of FsstatTasks.
+    """
+    return [FsstatTask() for _ in evidence]
+
+
+manager.JobsManager.RegisterJob(FsstatJob)
\ No newline at end of file
diff --git a/turbinia/jobs/photorec.py b/turbinia/jobs/photorec.py
index fa204ac9f..8e8541ea7 100644
--- a/turbinia/jobs/photorec.py
+++ b/turbinia/jobs/photorec.py
@@ -29,13 +29,13 @@ class PhotorecJob(interface.TurbiniaJob):
   NAME = 'PhotorecJob'
 
   def create_tasks(self, evidence):
-    """Create task for Plaso.
+    """Create task for Photorec.
 
     Args:
       evidence: List of evidence objects to process
 
     Returns:
-        A list of PlasoTasks.
+        A list of PhotorecTasks.
     """
     return [PhotorecTask() for _ in evidence]
 
diff --git a/turbinia/workers/fsstat.py b/turbinia/workers/fsstat.py
new file mode 100644
index 000000000..9ae66ae64
--- /dev/null
+++ b/turbinia/workers/fsstat.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Task to run fsstat on disk partitions."""
+from __future__ import unicode_literals
+
+import os
+
+from turbinia import TurbiniaException
+from turbinia.workers import TurbiniaTask
+from turbinia.evidence import EvidenceState as state
+from turbinia.evidence import ReportText
+
+
+class FsstatTask(TurbiniaTask):
+
+  REQUIRED_STATES = [state.ATTACHED]
+
+  def run(self, evidence, result):
+    """Task to execute fsstat.
+
+    Args:
+        evidence (Evidence object):  The evidence we will process.
+        result (TurbiniaTaskResult): The object to place task results into.
+
+    Returns:
+        TurbiniaTaskResult object.
+    """
+    fsstat_output = os.path.join(self.output_dir, 'fsstat.txt')
+    output_evidence = ReportText(source_path=fsstat_output)
+    cmd = ['sudo', 'fsstat', evidence.device_path]
+    result.log('Running fsstat as [{0!s}]'.format(cmd))
+    self.execute(
+        cmd, result, stdout_file=fsstat_output, new_evidence=[output_evidence],
+        close=True)
+
+    return result
\ No newline at end of file


Test patch:
diff --git a/turbinia/workers/fsstat_test.py b/turbinia/workers/fsstat_test.py
new file mode 100644
index 000000000..880f6ab3c
--- /dev/null
+++ b/turbinia/workers/fsstat_test.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for the Fsstat job."""
+
+from __future__ import unicode_literals
+
+import os
+import unittest
+import textwrap
+import mock
+
+from turbinia.evidence import ReportText
+from turbinia.workers import fsstat
+from turbinia.workers.workers_test import TestTurbiniaTaskBase
+from turbinia.workers import TurbiniaTaskResult
+
+
+class FsstatTaskTest(TestTurbiniaTaskBase):
+  """Tests for Fsstat."""
+
+  def setUp(self):
+    # pylint: disable=arguments-differ
+    super(FsstatTaskTest, self).setUp(
+        task_class=fsstat.FsstatTask, evidence_class=ReportText)
+    self.setResults(mock_run=False)
+    self.task.output_dir = self.task.base_output_dir
+
+  def testFsstatRun(self):
+    """Test fsstat task run."""
+    self.task.execute = mock.MagicMock(return_value=0)
+    result = self.task.run(self.evidence, self.result)
+
+    # Ensure execute method is being called.
+    self.task.execute.assert_called_once()
+    # Ensure run method returns a TurbiniaTaskResult instance.
+    self.assertIsInstance(result, TurbiniaTaskResult)
+
+
+if __name__ == '__main__':
+  unittest.main()