diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java index 48488dd82a..8d56b676cc 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java @@ -71,7 +71,11 @@ final class AddLogicalImageTask implements Runnable { private final Case currentCase; private volatile boolean cancelled; - + private boolean addingInterestingFiles; + private AddMultipleImageTask addMultipleImageTask; + private Thread multipleImageThread; + private boolean createVHD; + AddLogicalImageTask(String deviceId, String timeZone, File src, File dest, @@ -134,10 +138,6 @@ final class AddLogicalImageTask implements Runnable { return; } - if (cancelled) { - return; - } - progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(resultsFilename)); String status = addReport(Paths.get(dest.toString(), resultsFilename), resultsFilename + " " + src.getName()); if (status != null) { @@ -171,13 +171,13 @@ final class AddLogicalImageTask implements Runnable { } } - AddMultipleImageTask addMultipleImageTask = null; + addMultipleImageTask = null; + AddDataSourceCallback privateCallback = null; List newDataSources = new ArrayList<>(); - boolean createVHD; if (imagePaths.isEmpty()) { createVHD = false; - // No VHD in src directory, try ingest the root directory using Logical File Set + // No VHD in src directory, try ingest the root directory as local files File root = Paths.get(dest.toString(), ROOT_STR).toFile(); if (root.exists() && root.isDirectory()) { imagePaths.add(root.getAbsolutePath()); @@ -202,12 +202,10 @@ final class AddLogicalImageTask implements Runnable { createVHD = true; // ingest the VHDs try { - addMultipleImageTask = new AddMultipleImageTask(deviceId, imagePaths, timeZone , progressMonitor, callback); - addMultipleImageTask.run(); - if (addMultipleImageTask.getResult() == DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS) { - callback.done(addMultipleImageTask.getResult(), addMultipleImageTask.getErrorMessages(), addMultipleImageTask.getNewDataSources()); - return; - } + privateCallback = new AddDataSourceCallback(); + addMultipleImageTask = new AddMultipleImageTask(deviceId, imagePaths, timeZone , progressMonitor, privateCallback); + multipleImageThread = new Thread(addMultipleImageTask); + multipleImageThread.start(); } catch (NoCurrentCaseException ex) { String msg = Bundle.AddLogicalImageTask_noCurrentCase(); errorList.add(msg); @@ -217,11 +215,40 @@ final class AddLogicalImageTask implements Runnable { } try { + if (createVHD) { + // Wait for addMultipleImageTask to finish, via its privateCallback + while (privateCallback.inProgress()) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + // Got the addMultipleImageTask stop (cancel) + LOGGER.log(Level.INFO, "AddMultipleImageTask interrupted", ex); // NON-NLS + // now wait for addMultipleImageTask to revert and finally callback + while (privateCallback.inProgress()) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex2) { + LOGGER.log(Level.INFO, "AddMultipleImageTask interrupted 2", ex2); // NON-NLS + } + } + } + } + // TODO: Delete destination directory when 5453 (VHD file is not closed upon revert) is fixed. + // if (cancelled) { + // deleteDestinationDirectory(); + // } + if (privateCallback.getResult() == DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS) { + // bait out + callback.done(privateCallback.getResult(), privateCallback.getErrorMessages(), privateCallback.getNewDataSources()); + return; + } + } progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingInterestingFiles()); + addingInterestingFiles = true; addInterestingFiles(dest, Paths.get(dest.toString(), resultsFilename), createVHD); progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingInterestingFiles()); - if (addMultipleImageTask != null) { - callback.done(addMultipleImageTask.getResult(), addMultipleImageTask.getErrorMessages(), addMultipleImageTask.getNewDataSources()); + if (addMultipleImageTask != null && privateCallback != null) { + callback.done(privateCallback.getResult(), privateCallback.getErrorMessages(), privateCallback.getNewDataSources()); } else { callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, errorList, newDataSources); } @@ -265,6 +292,17 @@ final class AddLogicalImageTask implements Runnable { void cancelTask() { LOGGER.log(Level.WARNING, "AddLogicalImageTask cancelled, processing may be incomplete"); // NON-NLS cancelled = true; + if (addMultipleImageTask != null) { + multipleImageThread.interrupt(); + addMultipleImageTask.cancelTask(); + } + if (!createVHD) { + // Don't delete destination directory once we started adding interesting files. + // At this point the database and destination directory are complete. + if (!addingInterestingFiles) { + deleteDestinationDirectory(); + } + } } private Map imagePathsToDataSourceObjId(Map> imagePaths) { @@ -429,4 +467,51 @@ final class AddLogicalImageTask implements Runnable { } } + private boolean deleteDestinationDirectory() { + try { + FileUtils.deleteDirectory(dest); + LOGGER.log(Level.INFO, String.format("Cancellation: Deleted directory %s", dest.toString())); // NON-NLS + return true; + } catch (IOException ex) { + LOGGER.log(Level.WARNING, String.format("Cancellation: Failed to delete directory %s", dest.toString()), ex); // NON-NLS + return false; + } + } + + private class AddDataSourceCallback extends DataSourceProcessorCallback { + private List errorMessages; + private List newDataSources; + private DataSourceProcessorResult result; + private boolean inProgress = true; + + @Override + public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List errorMessages, List newDataSources) { + LOGGER.log(Level.INFO, "privateCallback done"); // NON-NLS + this.errorMessages = errorMessages; + this.newDataSources = newDataSources; + this.result = result; + this.inProgress = false; + } + + @Override + public void doneEDT(DataSourceProcessorResult result, List errorMessages, List newDataSources) { + done(result, errorMessages, newDataSources); + } + + public List getNewDataSources() { + return newDataSources; + } + + public List getErrorMessages() { + return errorMessages; + } + + public DataSourceProcessorResult getResult() { + return result; + } + + private boolean inProgress() { + return inProgress; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java index 5bba555647..b27b1e266d 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddMultipleImageTask.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.logicalimager.dsp; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; +import javax.annotation.concurrent.GuardedBy; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -56,13 +57,18 @@ class AddMultipleImageTask implements Runnable { private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; private final Case currentCase; - private boolean criticalErrorOccurred; - private volatile boolean cancelled; + private SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess = null; - private List newDataSources; - private List errorMessages; - private DataSourceProcessorResult result; + /* + * The cancellation requested flag and SleuthKit add image process are + * guarded by a lock to synchronize cancelling the process (setting the flag + * and calling its stop method) and calling either its commit or revert + * method. + */ + private final Object tskAddImageProcessLock; + @GuardedBy("tskAddImageProcessLock") + private boolean tskAddImageProcessStopped; /** * Constructs a runnable that adds multiple image files to a case database. @@ -95,36 +101,53 @@ class AddMultipleImageTask implements Runnable { this.progressMonitor = progressMonitor; currentCase = Case.getCurrentCaseThrows(); this.criticalErrorOccurred = false; - this.result = DataSourceProcessorResult.NO_ERRORS; + tskAddImageProcessLock = new Object(); } + @Messages({ + "AddMultipleImageTask.cancelled=Cancellation: Add image process reverted", + }) @Override public void run() { - newDataSources = new ArrayList<>(); - errorMessages = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + List newDataSources = new ArrayList<>(); + List emptyDataSources = new ArrayList<>(); /* * Try to add the input image files as images. */ List corruptedImageFilePaths = new ArrayList<>(); - currentCase.getSleuthkitCase().acquireSingleUserCaseWriteLock(); try { + currentCase.getSleuthkitCase().acquireSingleUserCaseWriteLock(); progressMonitor.setIndeterminate(true); for (String imageFilePath : imageFilePaths) { - if (!cancelled) { - addImageToCase(imageFilePath, newDataSources, corruptedImageFilePaths, errorMessages); + synchronized (tskAddImageProcessLock) { + if (!tskAddImageProcessStopped) { + addImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, false, false, ""); + } else { + return; + } + } + run(imageFilePath, corruptedImageFilePaths, errorMessages); + commitOrRevertAddImageProcess(imageFilePath, errorMessages, newDataSources); + synchronized (tskAddImageProcessLock) { + if (tskAddImageProcessStopped) { + errorMessages.add(Bundle.AddMultipleImageTask_cancelled()); + callback.done(DataSourceProcessorResult.CRITICAL_ERRORS, errorMessages, emptyDataSources); + return; + } } } } finally { currentCase.getSleuthkitCase().releaseSingleUserCaseWriteLock(); } - + /* * Try to add any input image files that did not have file systems as a * single an unallocated space file with the device id as the root virtual * directory name. */ - if (!cancelled && !corruptedImageFilePaths.isEmpty()) { + if (!tskAddImageProcessStopped && !corruptedImageFilePaths.isEmpty()) { SleuthkitCase caseDatabase; caseDatabase = currentCase.getSleuthkitCase(); try { @@ -146,7 +169,6 @@ class AddMultipleImageTask implements Runnable { start += TWO_GB; sequence++; } - } double leftoverSize = imageSize - sequence * TWO_GB; fileRanges.add(new TskFileRange(start, (long)leftoverSize, sequence)); @@ -170,6 +192,7 @@ class AddMultipleImageTask implements Runnable { /* * Pass the results back via the callback. */ + DataSourceProcessorCallback.DataSourceProcessorResult result; if (criticalErrorOccurred) { result = DataSourceProcessorResult.CRITICAL_ERRORS; } else if (!errorMessages.isEmpty()) { @@ -177,6 +200,7 @@ class AddMultipleImageTask implements Runnable { } else { result = DataSourceProcessorResult.NO_ERRORS; } + callback.done(result, errorMessages, newDataSources); } /** @@ -185,16 +209,30 @@ class AddMultipleImageTask implements Runnable { */ void cancelTask() { LOGGER.log(Level.WARNING, "AddMultipleImageTask cancelled, processing may be incomplete"); // NON-NLS - cancelled = true; + synchronized (tskAddImageProcessLock) { + tskAddImageProcessStopped = true; + if (addImageProcess != null) { + try { + /* + * All this does is set a flag that will make the TSK add + * image process exit when the flag is checked between + * processing steps. The state of the flag is not + * accessible, so record it here so that it is known that + * the revert method of the process object needs to be + * called. + */ + addImageProcess.stop(); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Cancellation: addImagePRocess.stop failed", ex); // NON-NLS + } + } + } } /** * Attempts to add an input image to the case. * * @param imageFilePath The image file path. - * @param newDataSources If the image is added, a data source is - * added to this list for eventual return to - * the caller via the callback. * @param corruptedImageFilePaths If the image cannot be added because * Sleuth Kit cannot detect a filesystem, * the image file path is added to this list @@ -209,13 +247,11 @@ class AddMultipleImageTask implements Runnable { "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.criticalErrorAdding=Critical error adding {0} for device {1}: {2}", "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.criticalErrorReverting=Critical error reverting add image process for {0} for device {1}: {2}", "# {0} - imageFilePath", "# {1} - deviceId", "# {2} - exceptionMessage", "AddMultipleImageTask.nonCriticalErrorAdding=Non-critical error adding {0} for device {1}: {2}",}) - private void addImageToCase(String imageFilePath, List newDataSources, List corruptedImageFilePaths, List errorMessages) { + private void run(String imageFilePath, List corruptedImageFilePaths, List errorMessages) { /* * Try to add the image to the case database as a data source. */ progressMonitor.setProgressText(Bundle.AddMultipleImageTask_adding(imageFilePath)); - SleuthkitCase caseDatabase = currentCase.getSleuthkitCase(); - SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess = caseDatabase.makeAddImageProcess(timeZone, false, false, ""); try { addImageProcess.run(deviceId, new String[]{imageFilePath}); } catch (TskCoreException ex) { @@ -231,59 +267,64 @@ class AddMultipleImageTask implements Runnable { errorMessages.add(Bundle.AddMultipleImageTask_criticalErrorAdding(imageFilePath, deviceId, ex.getLocalizedMessage())); criticalErrorOccurred = true; } - /* - * Either way, the add image process needs to be reverted. - */ - try { - addImageProcess.revert(); - } catch (TskCoreException e) { - errorMessages.add(Bundle.AddMultipleImageTask_criticalErrorReverting(imageFilePath, deviceId, e.getLocalizedMessage())); - criticalErrorOccurred = true; - } - return; } catch (TskDataException ex) { errorMessages.add(Bundle.AddMultipleImageTask_nonCriticalErrorAdding(imageFilePath, deviceId, ex.getLocalizedMessage())); } - - /* - * Try to commit the results of the add image process, retrieve the new - * image from the case database, and add it to the list of new data - * sources to be returned via the callback. - */ - try { - long imageId = addImageProcess.commit(); - Image dataSource = caseDatabase.getImageById(imageId); - newDataSources.add(dataSource); - - /* - * Verify the size of the new image. Note that it may not be what is - * expected, but at least part of it was added to the case. - */ - String verificationError = dataSource.verifyImageSize(); - if (!verificationError.isEmpty()) { - errorMessages.add(Bundle.AddMultipleImageTask_nonCriticalErrorAdding(imageFilePath, deviceId, verificationError)); + } + + /** + * Commits or reverts the results of the TSK add image process. If the + * process was stopped before it completed or there was a critical error the + * results are reverted, otherwise they are committed. + * + * @param imageFilePath The image file path. + * @param errorMessages Error messages, if any, are added to this list for + * eventual return via the callback. + * @param newDataSources If the new image is successfully committed, it is + * added to this list for eventual return via the + * callback. + */ + private void commitOrRevertAddImageProcess(String imageFilePath, List errorMessages, List newDataSources) { + synchronized (tskAddImageProcessLock) { + if (tskAddImageProcessStopped || criticalErrorOccurred) { + try { + addImageProcess.revert(); + } catch (TskCoreException ex) { + errorMessages.add(Bundle.AddMultipleImageTask_criticalErrorReverting(imageFilePath, deviceId, ex.getLocalizedMessage())); + criticalErrorOccurred = true; + } + return; + } + + if (!tskAddImageProcessStopped) { + /* + * Try to commit the results of the add image process, retrieve the new + * image from the case database, and add it to the list of new data + * sources to be returned via the callback. + */ + try { + long imageId = addImageProcess.commit(); + Image dataSource = currentCase.getSleuthkitCase().getImageById(imageId); + newDataSources.add(dataSource); + + /* + * Verify the size of the new image. Note that it may not be what is + * expected, but at least part of it was added to the case. + */ + String verificationError = dataSource.verifyImageSize(); + if (!verificationError.isEmpty()) { + errorMessages.add(Bundle.AddMultipleImageTask_nonCriticalErrorAdding(imageFilePath, deviceId, verificationError)); + } + } catch (TskCoreException ex) { + /* + * The add image process commit failed or querying the case database + * for the newly added image failed. Either way, this is a critical + * error. + */ + errorMessages.add(Bundle.AddMultipleImageTask_criticalErrorAdding(imageFilePath, deviceId, ex.getLocalizedMessage())); + criticalErrorOccurred = true; + } } - } catch (TskCoreException ex) { - /* - * The add image process commit failed or querying the case database - * for the newly added image failed. Either way, this is a critical - * error. - */ - errorMessages.add(Bundle.AddMultipleImageTask_criticalErrorAdding(imageFilePath, deviceId, ex.getLocalizedMessage())); - criticalErrorOccurred = true; } } - - public List getNewDataSources() { - return newDataSources; - } - - public List getErrorMessages() { - return errorMessages; - } - - public DataSourceProcessorResult getResult() { - return result; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java index 058c0fa2ee..c5753d9240 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java @@ -25,8 +25,9 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.UUID; +import javax.swing.JOptionPane; +import static javax.swing.JOptionPane.YES_OPTION; import javax.swing.JPanel; -import org.apache.commons.io.FileUtils; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; @@ -51,6 +52,7 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { private static final String LOGICAL_IMAGER_DIR = "LogicalImager"; //NON-NLS private final LogicalImagerPanel configPanel; private AddLogicalImageTask addLogicalImageTask; + private Thread thread; /* * Constructs a Logical Imager data source processor that implements the @@ -163,10 +165,20 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { File dest = Paths.get(logicalImagerDir.toString(), imageDirPath.getFileName().toString()).toFile(); if (dest.exists()) { // Destination directory already exists - String msg = Bundle.LogicalImagerDSProcessor_directoryAlreadyExists(dest.toString()); - errorList.add(msg); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); - return; + int showConfirmDialog = JOptionPane.showConfirmDialog(configPanel, + String.format("The logical imager folder %s already exists,\ndo you want to add it again using a new folder name?", dest.toString()), + "Destination directory confirmation", + JOptionPane.YES_NO_OPTION); + if (showConfirmDialog == YES_OPTION) { + // Get unique dest directory + String uniqueDirectory = imageDirPath.getFileName() + "_" + UUID.randomUUID(); + dest = Paths.get(logicalImagerDir.toString(), uniqueDirectory).toFile(); + } else { + String msg = Bundle.LogicalImagerDSProcessor_directoryAlreadyExists(dest.toString()); + errorList.add(msg); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); + return; + } } File src = imageDirPath.toFile(); @@ -207,12 +219,14 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { ) throws NoCurrentCaseException { addLogicalImageTask = new AddLogicalImageTask(deviceId, timeZone, src, dest, progressMonitor, callback); - new Thread(addLogicalImageTask).start(); + thread = new Thread(addLogicalImageTask); + thread.start(); } @Override public void cancel() { if (addLogicalImageTask != null) { + thread.interrupt(); addLogicalImageTask.cancelTask(); } }