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