Autopsy 4.22.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 */
19package org.sleuthkit.autopsy.imagewriter;
20
21import com.google.common.util.concurrent.ThreadFactoryBuilder;
22import java.beans.PropertyChangeEvent;
23import java.beans.PropertyChangeListener;
24import java.util.EnumSet;
25import java.util.Set;
26import java.util.concurrent.Callable;
27import java.util.concurrent.Executors;
28import java.util.concurrent.Future;
29import java.util.concurrent.ScheduledFuture;
30import java.util.concurrent.ScheduledThreadPoolExecutor;
31import java.util.concurrent.TimeUnit;
32import java.util.concurrent.ExecutionException;
33import java.util.concurrent.ExecutorService;
34import java.util.logging.Level;
35import org.netbeans.api.progress.ProgressHandle;
36import org.openide.util.NbBundle.Messages;
37import org.sleuthkit.autopsy.casemodule.Case;
38import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
39import org.sleuthkit.autopsy.core.RuntimeProperties;
40import org.sleuthkit.autopsy.coreutils.Logger;
41import org.sleuthkit.datamodel.Image;
42import org.sleuthkit.datamodel.SleuthkitCase;
43import org.sleuthkit.autopsy.ingest.IngestManager;
44import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
45import org.sleuthkit.datamodel.SleuthkitJNI;
46import org.sleuthkit.datamodel.TskCoreException;
47
56class 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
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}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static synchronized IngestManager getInstance()
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(final PropertyChangeListener listener)

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