Autopsy  4.19.3
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.concurrent.ExecutorService;
34 import java.util.logging.Level;
35 import org.netbeans.api.progress.ProgressHandle;
36 import org.openide.util.NbBundle.Messages;
41 import org.sleuthkit.datamodel.Image;
42 import org.sleuthkit.datamodel.SleuthkitCase;
45 import org.sleuthkit.datamodel.SleuthkitJNI;
46 import org.sleuthkit.datamodel.TskCoreException;
47 
56 class ImageWriter implements PropertyChangeListener {
57 
58  private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED);
59  private static final Logger logger = Logger.getLogger(ImageWriter.class.getName());
60  private final Long dataSourceId;
61  private final ImageWriterSettings settings;
62 
63  private Long imageHandle = null;
64  private Future<Integer> finishTask = null;
65  private ProgressHandle progressHandle = null;
66  private ScheduledFuture<?> progressUpdateTask = null;
67  private boolean isCancelled = false;
68  private boolean isStarted = false;
69  private final Object currentTasksLock = new Object(); // Get this lock before accessing imageHandle, finishTask, progressHandle, progressUpdateTask,
70  // isCancelled, isStarted, or isFinished
71 
72  private ScheduledThreadPoolExecutor periodicTasksExecutor = null;
73  private final boolean doUI;
74  private SleuthkitCase caseDb = null;
75 
82  ImageWriter(Long dataSourceId, ImageWriterSettings settings) {
83  this.dataSourceId = dataSourceId;
84  this.settings = settings;
86 
87  // We save the reference to the sleuthkit case here in case getOpenCase() is set to
88  // null before Image Writer finishes. The user can still elect to wait for image writer
89  // (in ImageWriterService.closeCaseResources) even though the case is closing.
90  try {
92  } catch (NoCurrentCaseException ex) {
93  logger.log(Level.SEVERE, "Unable to load case. Image writer will be cancelled.");
94  this.isCancelled = true;
95  }
96  }
97 
101  void subscribeToEvents() {
102  IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, this);
103  }
104 
109  void unsubscribeFromEvents() {
111  }
112 
117  @Override
118  public void propertyChange(PropertyChangeEvent evt) {
119  if (evt instanceof DataSourceAnalysisCompletedEvent) {
120 
121  DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent) evt;
122 
123  if (event.getDataSource() != null) {
124  long imageId = event.getDataSource().getId();
125  String name = event.getDataSource().getName();
126 
127  // Check that the event corresponds to this datasource
128  if (imageId != dataSourceId) {
129  return;
130  }
131  new Thread(() -> {
132  startFinishImage(name);
133  }).start();
134 
135  } else {
136  logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS
137  }
138  }
139  }
140 
141  @Messages({
142  "# {0} - data source name",
143  "ImageWriter.progressBar.message=Finishing acquisition of {0} (unplug device to cancel)"
144  })
145  private void startFinishImage(String dataSourceName) {
146 
147  ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("ImageWriter-startFinishImage-%d").build());
148  synchronized (currentTasksLock) {
149  if (isCancelled) {
150  return;
151  }
152 
153  // If we've already started the finish process for this datasource, return.
154  // Multiple DataSourceAnalysisCompletedEvent events can come from
155  // the same image if more ingest modules are run later
156  if (isStarted) {
157  return;
158  }
159 
160  Image image;
161  try {
162  image = Case.getCurrentCaseThrows().getSleuthkitCase().getImageById(dataSourceId);
163  imageHandle = image.getImageHandle();
164  } catch (NoCurrentCaseException ex) {
165  // This exception means that getOpenCase() failed because no case was open.
166  // This can happen when the user closes the case while ingest is ongoing - canceling
167  // ingest fires off the DataSourceAnalysisCompletedEvent while the case is in the
168  // process of closing.
169  logger.log(Level.WARNING, String.format("Case closed before ImageWriter could start the finishing process for %s",
170  dataSourceName));
171  return;
172  } catch (TskCoreException ex) {
173  logger.log(Level.SEVERE, "Error loading image", ex);
174  return;
175  }
176 
177  logger.log(Level.INFO, String.format("Finishing VHD image for %s",
178  dataSourceName)); //NON-NLS
179 
180  if (doUI) {
181  periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS
182  progressHandle = ProgressHandle.createHandle(Bundle.ImageWriter_progressBar_message(dataSourceName));
183  progressHandle.start(100);
184  progressUpdateTask = periodicTasksExecutor.scheduleWithFixedDelay(
185  new ProgressUpdateTask(progressHandle, imageHandle), 0, 250, TimeUnit.MILLISECONDS);
186  }
187 
188  // The added complexity here with the Future is because we absolutely need to make sure
189  // the call to finishImageWriter returns before allowing the TSK data structures to be freed
190  // during case close.
191  finishTask = executor.submit(new Callable<Integer>() {
192  @Override
193  public Integer call() throws TskCoreException {
194  try {
195  int result = SleuthkitJNI.finishImageWriter(imageHandle);
196 
197  // We've decided to always update the path to the VHD, even if it wasn't finished.
198  // This supports the case where an analyst has partially ingested a device
199  // but has to stop before completion. They will at least have part of the image.
200  if (settings.getUpdateDatabasePath()) {
201  caseDb.updateImagePath(settings.getPath(), dataSourceId);
202  }
203  return result;
204  } catch (Throwable ex) {
205  logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
206  return -1;
207  }
208  }
209  });
210 
211  // Setting this means that finishTask and all the UI updaters are initialized (if running UI)
212  isStarted = true;
213  }
214 
215  // Wait for finishImageWriter to complete
216  int result = 0;
217  try {
218  // The call to get() can happen multiple times if the user closes the case, which is ok
219  result = finishTask.get();
220  executor.shutdownNow();
221  } catch (InterruptedException | ExecutionException ex) {
222  logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
223  }
224 
225  synchronized (currentTasksLock) {
226  if (doUI) {
227  // Some of these may be called twice if the user closes the case
228  progressUpdateTask.cancel(true);
229  progressHandle.finish();
230  periodicTasksExecutor.shutdown();
231  }
232  }
233 
234  if (result == 0) {
235  logger.log(Level.INFO, String.format("Successfully finished writing VHD image for %s", dataSourceName)); //NON-NLS
236  } else {
237  logger.log(Level.INFO, String.format("Finished VHD image for %s with errors", dataSourceName)); //NON-NLS
238  }
239  }
240 
246  void cancelIfNotStarted() {
247  synchronized (currentTasksLock) {
248  if (!isStarted) {
249  isCancelled = true;
250  }
251  }
252  }
253 
260  boolean jobIsInProgress() {
261  synchronized (currentTasksLock) {
262  return ((isStarted) && (!finishTask.isDone()));
263  }
264  }
265 
270  void cancelJob() {
271  synchronized (currentTasksLock) {
272  // All of the following is redundant but safe to call on a complete job
273  isCancelled = true;
274 
275  if (isStarted) {
276  SleuthkitJNI.cancelFinishImage(imageHandle);
277 
278  // Stop the progress bar update task.
279  // The thread from startFinishImage will also stop it
280  // once the task completes, but we don't have a guarantee on
281  // when that happens.
282  // Since we've stopped the update task, we'll stop the associated progress
283  // bar now, too.
284  if (doUI) {
285  progressUpdateTask.cancel(true);
286  progressHandle.finish();
287  }
288  }
289  }
290  }
291 
296  void waitForJobToFinish() {
297  synchronized (currentTasksLock) {
298  // Wait for the finish task to end
299  if (isStarted) {
300  try {
301  finishTask.get();
302  } catch (InterruptedException | ExecutionException ex) {
303  Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
304  }
305  if (doUI) {
306  progressUpdateTask.cancel(true);
307  }
308  }
309  }
310  }
311 
315  private final class ProgressUpdateTask implements Runnable {
316 
317  final long imageHandle;
318  final ProgressHandle progressHandle;
319 
320  ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle) {
321  this.imageHandle = imageHandle;
322  this.progressHandle = progressHandle;
323  }
324 
325  @Override
326  public void run() {
327  try {
328  int progress = SleuthkitJNI.getFinishImageProgress(imageHandle);
329  progressHandle.progress(progress);
330  } catch (Exception ex) {
331  logger.log(Level.SEVERE, "Unexpected exception in ProgressUpdateTask", ex); //NON-NLS
332  }
333  }
334  }
335 }
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-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.