Autopsy  4.19.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ImageWriter.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.imagewriter;
20 
21 import com.google.common.util.concurrent.ThreadFactoryBuilder;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.util.EnumSet;
25 import java.util.Set;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.Future;
29 import java.util.concurrent.ScheduledFuture;
30 import java.util.concurrent.ScheduledThreadPoolExecutor;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.ExecutionException;
33 import java.util.logging.Level;
34 import org.netbeans.api.progress.ProgressHandle;
35 import org.openide.util.NbBundle.Messages;
40 import org.sleuthkit.datamodel.Image;
41 import org.sleuthkit.datamodel.SleuthkitCase;
44 import org.sleuthkit.datamodel.SleuthkitJNI;
45 import org.sleuthkit.datamodel.TskCoreException;
46 
55 class ImageWriter implements PropertyChangeListener {
56 
57  private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED);
58  private static final Logger logger = Logger.getLogger(ImageWriter.class.getName());
59  private final Long dataSourceId;
60  private final ImageWriterSettings settings;
61 
62  private Long imageHandle = null;
63  private Future<Integer> finishTask = null;
64  private ProgressHandle progressHandle = null;
65  private ScheduledFuture<?> progressUpdateTask = null;
66  private boolean isCancelled = false;
67  private boolean isStarted = false;
68  private final Object currentTasksLock = new Object(); // Get this lock before accessing imageHandle, finishTask, progressHandle, progressUpdateTask,
69  // isCancelled, isStarted, or isFinished
70 
71  private ScheduledThreadPoolExecutor periodicTasksExecutor = null;
72  private final boolean doUI;
73  private SleuthkitCase caseDb = null;
74 
81  ImageWriter(Long dataSourceId, ImageWriterSettings settings) {
82  this.dataSourceId = dataSourceId;
83  this.settings = settings;
85 
86  // We save the reference to the sleuthkit case here in case getOpenCase() is set to
87  // null before Image Writer finishes. The user can still elect to wait for image writer
88  // (in ImageWriterService.closeCaseResources) even though the case is closing.
89  try {
91  } catch (NoCurrentCaseException ex) {
92  logger.log(Level.SEVERE, "Unable to load case. Image writer will be cancelled.");
93  this.isCancelled = true;
94  }
95  }
96 
100  void subscribeToEvents() {
101  IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, this);
102  }
103 
108  void unsubscribeFromEvents() {
110  }
111 
116  @Override
117  public void propertyChange(PropertyChangeEvent evt) {
118  if (evt instanceof DataSourceAnalysisCompletedEvent) {
119 
120  DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent) evt;
121 
122  if (event.getDataSource() != null) {
123  long imageId = event.getDataSource().getId();
124  String name = event.getDataSource().getName();
125 
126  // Check that the event corresponds to this datasource
127  if (imageId != dataSourceId) {
128  return;
129  }
130  new Thread(() -> {
131  startFinishImage(name);
132  }).start();
133 
134  } else {
135  logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS
136  }
137  }
138  }
139 
140  @Messages({
141  "# {0} - data source name",
142  "ImageWriter.progressBar.message=Finishing acquisition of {0} (unplug device to cancel)"
143  })
144  private void startFinishImage(String dataSourceName) {
145 
146  synchronized (currentTasksLock) {
147  if (isCancelled) {
148  return;
149  }
150 
151  // If we've already started the finish process for this datasource, return.
152  // Multiple DataSourceAnalysisCompletedEvent events can come from
153  // the same image if more ingest modules are run later
154  if (isStarted) {
155  return;
156  }
157 
158  Image image;
159  try {
160  image = Case.getCurrentCaseThrows().getSleuthkitCase().getImageById(dataSourceId);
161  imageHandle = image.getImageHandle();
162  } catch (NoCurrentCaseException ex) {
163  // This exception means that getOpenCase() failed because no case was open.
164  // This can happen when the user closes the case while ingest is ongoing - canceling
165  // ingest fires off the DataSourceAnalysisCompletedEvent while the case is in the
166  // process of closing.
167  logger.log(Level.WARNING, String.format("Case closed before ImageWriter could start the finishing process for %s",
168  dataSourceName));
169  return;
170  } catch (TskCoreException ex) {
171  logger.log(Level.SEVERE, "Error loading image", ex);
172  return;
173  }
174 
175  logger.log(Level.INFO, String.format("Finishing VHD image for %s",
176  dataSourceName)); //NON-NLS
177 
178  if (doUI) {
179  periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS
180  progressHandle = ProgressHandle.createHandle(Bundle.ImageWriter_progressBar_message(dataSourceName));
181  progressHandle.start(100);
182  progressUpdateTask = periodicTasksExecutor.scheduleWithFixedDelay(
183  new ProgressUpdateTask(progressHandle, imageHandle), 0, 250, TimeUnit.MILLISECONDS);
184  }
185 
186  // The added complexity here with the Future is because we absolutely need to make sure
187  // the call to finishImageWriter returns before allowing the TSK data structures to be freed
188  // during case close.
189  finishTask = Executors.newSingleThreadExecutor().submit(new Callable<Integer>() {
190  @Override
191  public Integer call() throws TskCoreException {
192  try {
193  int result = SleuthkitJNI.finishImageWriter(imageHandle);
194 
195  // We've decided to always update the path to the VHD, even if it wasn't finished.
196  // This supports the case where an analyst has partially ingested a device
197  // but has to stop before completion. They will at least have part of the image.
198  if (settings.getUpdateDatabasePath()) {
199  caseDb.updateImagePath(settings.getPath(), dataSourceId);
200  }
201  return result;
202  } catch (TskCoreException ex) {
203  logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
204  return -1;
205  }
206  }
207  });
208 
209  // Setting this means that finishTask and all the UI updaters are initialized (if running UI)
210  isStarted = true;
211  }
212 
213  // Wait for finishImageWriter to complete
214  int result = 0;
215  try {
216  // The call to get() can happen multiple times if the user closes the case, which is ok
217  result = finishTask.get();
218  } catch (InterruptedException | ExecutionException ex) {
219  logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
220  }
221 
222  synchronized (currentTasksLock) {
223  if (doUI) {
224  // Some of these may be called twice if the user closes the case
225  progressUpdateTask.cancel(true);
226  progressHandle.finish();
227  periodicTasksExecutor.shutdown();
228  }
229  }
230 
231  if (result == 0) {
232  logger.log(Level.INFO, String.format("Successfully finished writing VHD image for %s", dataSourceName)); //NON-NLS
233  } else {
234  logger.log(Level.INFO, String.format("Finished VHD image for %s with errors", dataSourceName)); //NON-NLS
235  }
236  }
237 
243  void cancelIfNotStarted() {
244  synchronized (currentTasksLock) {
245  if (!isStarted) {
246  isCancelled = true;
247  }
248  }
249  }
250 
257  boolean jobIsInProgress() {
258  synchronized (currentTasksLock) {
259  return ((isStarted) && (!finishTask.isDone()));
260  }
261  }
262 
267  void cancelJob() {
268  synchronized (currentTasksLock) {
269  // All of the following is redundant but safe to call on a complete job
270  isCancelled = true;
271 
272  if (isStarted) {
273  SleuthkitJNI.cancelFinishImage(imageHandle);
274 
275  // Stop the progress bar update task.
276  // The thread from startFinishImage will also stop it
277  // once the task completes, but we don't have a guarantee on
278  // when that happens.
279  // Since we've stopped the update task, we'll stop the associated progress
280  // bar now, too.
281  if (doUI) {
282  progressUpdateTask.cancel(true);
283  progressHandle.finish();
284  }
285  }
286  }
287  }
288 
293  void waitForJobToFinish() {
294  synchronized (currentTasksLock) {
295  // Wait for the finish task to end
296  if (isStarted) {
297  try {
298  finishTask.get();
299  } catch (InterruptedException | ExecutionException ex) {
300  Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
301  }
302  if (doUI) {
303  progressUpdateTask.cancel(true);
304  }
305  }
306  }
307  }
308 
312  private final class ProgressUpdateTask implements Runnable {
313 
314  final long imageHandle;
315  final ProgressHandle progressHandle;
316 
317  ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle) {
318  this.imageHandle = imageHandle;
319  this.progressHandle = progressHandle;
320  }
321 
322  @Override
323  public void run() {
324  try {
325  int progress = SleuthkitJNI.getFinishImageProgress(imageHandle);
326  progressHandle.progress(progress);
327  } catch (Exception ex) {
328  logger.log(Level.SEVERE, "Unexpected exception in ProgressUpdateTask", ex); //NON-NLS
329  }
330  }
331  }
332 }
static synchronized IngestManager getInstance()
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.