Autopsy  4.22.0
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;
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()
static void cancelFinishImage(long imgHandle)
static int finishImageWriter(long imgHandle)
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(final PropertyChangeListener listener)
static int getFinishImageProgress(long imgHandle)
void updateImagePath(String newPath, long objectId)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
synchronized long getImageHandle()

Copyright © 2012-2024 Sleuth Kit Labs. Generated on: Mon Mar 17 2025
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.