From d3d08e22c30ce000cc90ab1de7e7da4efaaa4c47 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 6 Feb 2017 09:39:55 -0500 Subject: [PATCH 01/15] Adding option to copy local disk to data source selection --- .../autopsy/casemodule/AddImageTask.java | 9 ++- .../autopsy/casemodule/Bundle.properties | 4 + .../autopsy/casemodule/ImageDSProcessor.java | 2 +- .../casemodule/LocalDiskDSProcessor.java | 8 +- .../autopsy/casemodule/LocalDiskPanel.form | 60 ++++++++++++++- .../autopsy/casemodule/LocalDiskPanel.java | 77 ++++++++++++++++++- 6 files changed, 149 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 227bfd0650..e4f03757fb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -41,6 +41,7 @@ class AddImageTask implements Runnable { private final String deviceId; private final String imagePath; private final String timeZone; + private final String imageWriterPath; private final boolean ignoreFatOrphanFiles; private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; @@ -74,15 +75,19 @@ class AddImageTask implements Runnable { * java.util.TimeZone.getID. * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a * FAT filesystem. + * @param imageWriterPath Path that a copy of the image should be written to. + * Use empty string to disable image writing * @param progressMonitor Progress monitor to report progress during * processing. * @param callback Callback to call when processing is done. */ - AddImageTask(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddImageTask(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, String imageWriterPath, + DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; this.imagePath = imagePath; this.timeZone = timeZone; this.ignoreFatOrphanFiles = ignoreFatOrphanFiles; + this.imageWriterPath = imageWriterPath; this.callback = callback; this.progressMonitor = progressMonitor; tskAddImageProcessLock = new Object(); @@ -101,7 +106,7 @@ class AddImageTask implements Runnable { try { currentCase.getSleuthkitCase().acquireExclusiveLock(); synchronized (tskAddImageProcessLock) { - tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles); + tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles, imageWriterPath); } Thread progressUpdateThread = new Thread(new ProgressUpdater(progressMonitor, tskAddImageProcess)); progressUpdateThread.start(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 12184925e9..18c8263b72 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -241,3 +241,7 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close +LocalDiskPanel.copyImageCheckbox.text=Make a copy of the disk +LocalDiskPanel.imageWriterPathLabel.text=jLabel1 +LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the case directory as : +LocalDiskPanel.imageWriterErrorLabel.text=Error Label diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 1cceb7ab12..8837c496d0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -185,7 +185,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour * @param callback Callback to call when processing is done. */ public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addImageTask = new AddImageTask(deviceId, imagePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + addImageTask = new AddImageTask(deviceId, imagePath, timeZone, ignoreFatOrphanFiles, "", progressMonitor, callback); new Thread(addImageTask).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java index 015f7751ea..80d1306598 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -54,6 +54,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData private String deviceId; private String drivePath; private String timeZone; + private String imageWriterPath = ""; private boolean ignoreFatOrphanFiles; private boolean setDataSourceOptionsCalled; @@ -137,8 +138,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData drivePath = configPanel.getContentPaths(); timeZone = configPanel.getTimeZone(); ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); + if(configPanel.getImageWriterEnabled()){ + imageWriterPath = configPanel.getImageWriterPath(); + } } - addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, imageWriterPath, progressMonitor, callback); new Thread(addDiskTask).start(); } @@ -164,7 +168,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData * @param callback Callback to call when processing is done. */ public void run(String deviceId, String drivePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + addDiskTask = new AddImageTask(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, imageWriterPath, progressMonitor, callback); new Thread(addDiskTask).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index b1409546e6..4c711c0187 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -35,12 +35,21 @@ + - + + + + + + + + + - + @@ -61,7 +70,16 @@ - + + + + + + + + + + @@ -165,5 +183,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 0d485b3392..88a276494d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -22,6 +22,7 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; +import java.io.File; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -56,6 +57,7 @@ final class LocalDiskPanel extends JPanel { private static final long serialVersionUID = 1L; private List disks; private LocalDiskModel model; + private String fullImageWriterPath = ""; private boolean enableNext = false; /** @@ -106,6 +108,10 @@ final class LocalDiskPanel extends JPanel { timeZoneComboBox = new javax.swing.JComboBox<>(); noFatOrphansCheckbox = new javax.swing.JCheckBox(); descLabel = new javax.swing.JLabel(); + copyImageCheckbox = new javax.swing.JCheckBox(); + imageWriterPathLabel = new javax.swing.JLabel(); + imageWriterPathDescLabel = new javax.swing.JLabel(); + imageWriterErrorLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(485, 65)); @@ -132,6 +138,16 @@ final class LocalDiskPanel extends JPanel { descLabel.setFont(descLabel.getFont().deriveFont(descLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.descLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(copyImageCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.copyImageCheckbox.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathDescLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathDescLabel.text")); // NOI18N + + imageWriterErrorLabel.setFont(imageWriterErrorLabel.getFont().deriveFont(imageWriterErrorLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(imageWriterErrorLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterErrorLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -146,10 +162,17 @@ final class LocalDiskPanel extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(noFatOrphansCheckbox) + .addComponent(copyImageCheckbox) .addGroup(layout.createSequentialGroup() .addGap(21, 21, 21) - .addComponent(descLabel))) - .addGap(0, 102, Short.MAX_VALUE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(imageWriterPathDescLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGap(0, 12, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -167,14 +190,26 @@ final class LocalDiskPanel extends JPanel { .addComponent(noFatOrphansCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(descLabel) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(copyImageCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(imageWriterPathDescLabel) + .addComponent(imageWriterPathLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(imageWriterErrorLabel) + .addContainerGap(58, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox copyImageCheckbox; private javax.swing.JLabel descLabel; private javax.swing.JComboBox diskComboBox; private javax.swing.JLabel diskLabel; private javax.swing.JLabel errorLabel; + private javax.swing.JLabel imageWriterErrorLabel; + private javax.swing.JLabel imageWriterPathDescLabel; + private javax.swing.JLabel imageWriterPathLabel; private javax.swing.JCheckBox noFatOrphansCheckbox; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; @@ -214,6 +249,36 @@ final class LocalDiskPanel extends JPanel { boolean getNoFatOrphans() { return noFatOrphansCheckbox.isSelected(); } + + private void setPotentialImageWriterPath(LocalDisk disk){ + System.out.println("\n\nsetPotentialImageWriterPath"); + System.out.println(" name: " + disk.getName()); + System.out.println(" path: " + disk.getPath()); + String path = disk.getName().replaceAll("[:]", ""); + path += ".vhd"; + System.out.println(" Output file: " + path); + imageWriterPathLabel.setText(path); + fullImageWriterPath = Case.getCurrentCase().getCaseDirectory() + "\\" + path; + } + + private boolean imageWriterPathIsValid(){ + + File f = new File(fullImageWriterPath); + if(f.exists()) { + this.imageWriterErrorLabel.setText("Error - file already exists"); // Put in bundle!!!! + return false; + } + imageWriterErrorLabel.setText(""); + return true; + } + + boolean getImageWriterEnabled(){ + return copyImageCheckbox.isSelected(); + } + + String getImageWriterPath(){ + return fullImageWriterPath; + } /** * Should we enable the wizard's next button? Always return true because we @@ -222,6 +287,11 @@ final class LocalDiskPanel extends JPanel { * @return true */ public boolean validatePanel() { + if(copyImageCheckbox.isSelected() && + ! imageWriterPathIsValid()){ + return false; + } + return enableNext; } @@ -318,6 +388,7 @@ final class LocalDiskPanel extends JPanel { if (ready) { selected = (LocalDisk) anItem; enableNext = true; + setPotentialImageWriterPath((LocalDisk) selected); try { firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); From 149b3b594ded1a42613ee861d1addec0fd234c5f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Feb 2017 10:51:43 -0500 Subject: [PATCH 02/15] Improve UI for enabling image writer. --- .../sleuthkit/autopsy/casemodule/Bundle.properties | 3 ++- .../autopsy/casemodule/LocalDiskPanel.form | 2 +- .../autopsy/casemodule/LocalDiskPanel.java | 13 ++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 18c8263b72..914aa8a48d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -76,6 +76,7 @@ LocalDiskPanel.timeZoneLabel.text=Please select the input timezone: LocalDiskPanel.noFatOrphansCheckbox.toolTipText= LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched) +LocalDiskPanel.imageWriterError.text=Error - file already exists MissingImageDialog.browseButton.text=Browse MissingImageDialog.pathNameTextField.text= AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status @@ -241,7 +242,7 @@ LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close -LocalDiskPanel.copyImageCheckbox.text=Make a copy of the disk +LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed LocalDiskPanel.imageWriterPathLabel.text=jLabel1 LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the case directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index 4c711c0187..a8532bb1d4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -79,7 +79,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 88a276494d..676682b258 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -23,6 +23,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.io.File; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -90,6 +91,7 @@ final class LocalDiskPanel extends JPanel { errorLabel.setVisible(false); errorLabel.setText(""); diskComboBox.setEnabled(false); + imageWriterErrorLabel.setText(""); } /** @@ -198,7 +200,7 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterPathLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(58, Short.MAX_VALUE)) + .addContainerGap(63, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -251,21 +253,18 @@ final class LocalDiskPanel extends JPanel { } private void setPotentialImageWriterPath(LocalDisk disk){ - System.out.println("\n\nsetPotentialImageWriterPath"); - System.out.println(" name: " + disk.getName()); - System.out.println(" path: " + disk.getPath()); String path = disk.getName().replaceAll("[:]", ""); + path += " " + System.currentTimeMillis(); path += ".vhd"; - System.out.println(" Output file: " + path); imageWriterPathLabel.setText(path); - fullImageWriterPath = Case.getCurrentCase().getCaseDirectory() + "\\" + path; + fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); } private boolean imageWriterPathIsValid(){ File f = new File(fullImageWriterPath); if(f.exists()) { - this.imageWriterErrorLabel.setText("Error - file already exists"); // Put in bundle!!!! + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterError.text")); return false; } imageWriterErrorLabel.setText(""); From 46824179a0863a322ba77dc58fef287e0294d4ed Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Feb 2017 11:15:39 -0500 Subject: [PATCH 03/15] Working on moving Image Writer output to a subfolder of ModuleOutput --- .../autopsy/casemodule/LocalDiskPanel.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 676682b258..b7300c8f1c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -252,12 +252,24 @@ final class LocalDiskPanel extends JPanel { return noFatOrphansCheckbox.isSelected(); } + private static String getDefaultImageWriterFolder(){ + //return Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toString(); + return Paths.get(Case.getCurrentCase().getModuleDirectory()).toString(); // Don't want to commit in a broken state + } + private void setPotentialImageWriterPath(LocalDisk disk){ + + File subDirectory = Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toFile(); + if (!subDirectory.exists()) { + subDirectory.mkdirs(); + } + String path = disk.getName().replaceAll("[:]", ""); path += " " + System.currentTimeMillis(); path += ".vhd"; imageWriterPathLabel.setText(path); - fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); + //fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); + fullImageWriterPath = Paths.get(getDefaultImageWriterFolder(), path).toString(); } private boolean imageWriterPathIsValid(){ From 792f0a996a437eedfe3be6a7a75ce2096635520a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Feb 2017 14:56:43 -0500 Subject: [PATCH 04/15] Change default folder for VHD --- .../sleuthkit/autopsy/casemodule/Bundle.properties | 2 +- .../sleuthkit/autopsy/casemodule/LocalDiskPanel.form | 4 ++-- .../sleuthkit/autopsy/casemodule/LocalDiskPanel.java | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 914aa8a48d..f852c046e6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -244,5 +244,5 @@ IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed LocalDiskPanel.imageWriterPathLabel.text=jLabel1 -LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the case directory as : +LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the Module Output/Image Writer directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index a8532bb1d4..e43c8b65c9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -49,7 +49,7 @@ - + @@ -79,7 +79,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index b7300c8f1c..ed53684b0c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -92,6 +92,7 @@ final class LocalDiskPanel extends JPanel { errorLabel.setText(""); diskComboBox.setEnabled(false); imageWriterErrorLabel.setText(""); + imageWriterPathLabel.setText(""); } /** @@ -174,7 +175,7 @@ final class LocalDiskPanel extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGap(0, 12, Short.MAX_VALUE)) + .addGap(0, 0, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -200,7 +201,7 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterPathLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(63, Short.MAX_VALUE)) + .addContainerGap(124, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -253,13 +254,12 @@ final class LocalDiskPanel extends JPanel { } private static String getDefaultImageWriterFolder(){ - //return Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toString(); - return Paths.get(Case.getCurrentCase().getModuleDirectory()).toString(); // Don't want to commit in a broken state + return Paths.get(Case.getCurrentCase().getModuleDirectory(), "Image Writer").toString(); } private void setPotentialImageWriterPath(LocalDisk disk){ - File subDirectory = Paths.get(Case.getCurrentCase().getModuleDirectory(), "ImageWriter").toFile(); + File subDirectory = Paths.get(getDefaultImageWriterFolder()).toFile(); if (!subDirectory.exists()) { subDirectory.mkdirs(); } @@ -268,7 +268,6 @@ final class LocalDiskPanel extends JPanel { path += " " + System.currentTimeMillis(); path += ".vhd"; imageWriterPathLabel.setText(path); - //fullImageWriterPath = Paths.get(Case.getCurrentCase().getCaseDirectory(), path).toString(); fullImageWriterPath = Paths.get(getDefaultImageWriterFolder(), path).toString(); } From d6292a4f94c32bf8b8cf524dce9f6abee4df9c21 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 21 Feb 2017 15:01:25 -0500 Subject: [PATCH 05/15] Finishing the Image Writer finish method --- .../autopsy/casemodule/AddImageTask.java | 3 + .../autopsy/casemodule/Bundle.properties | 1 + .../sleuthkit/autopsy/casemodule/Case.java | 18 ++ .../autopsy/casemodule/ImageWriter.java | 251 ++++++++++++++++++ .../autopsy/casemodule/LocalDiskPanel.form | 14 +- .../autopsy/casemodule/LocalDiskPanel.java | 13 +- 6 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index e4f03757fb..cf7db4a392 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -206,6 +206,9 @@ class AddImageTask implements Runnable { if (!verificationError.isEmpty()) { errorMessages.add(verificationError); } + if(! imageWriterPath.isEmpty()){ + Case.getCurrentCase().scheduleImageWriterFinish(imageId); + } newDataSources.add(newImage); } else { String errorMessage = String.format("Error commiting adding image %s to the case database, no object id returned", imagePath); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index f852c046e6..12e2b15179 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -246,3 +246,4 @@ LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is LocalDiskPanel.imageWriterPathLabel.text=jLabel1 LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the Module Output/Image Writer directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label +LocalDiskPanel.jLabel1.text=Note that at least one ingest module must be run to create a complete copy diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index d681e81b48..f0c42d2fec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -286,6 +286,7 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); + private final ImageWriter imageWriter = new ImageWriter(); private static String appName; private static Case currentCase; private static CoordinationService.Lock currentCaseLock; @@ -1437,6 +1438,7 @@ public class Case implements SleuthkitCase.ErrorObserver { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); + oldCase.closeImageWriter(); completeCaseChange(null); //closes windows, etc if (null != oldCase.tskErrorReporter) { oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case @@ -1613,6 +1615,22 @@ public class Case implements SleuthkitCase.ErrorObserver { } return currentCaseExecutor; } + + /** + * Register the ID of an image that is being copied using ImageWriter. + * This will cause the image to be finished after ingest is complete. + * @param imageID The image ID + */ + void scheduleImageWriterFinish(long imageID){ + imageWriter.addDataSourceId(imageID); + } + + /** + * Cancel all tasks associated with Image Writer + */ + void closeImageWriter(){ + imageWriter.close(); + } /** * Gets the time zone(s) of the image data source(s) in this case. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java new file mode 100644 index 0000000000..6510c5231a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java @@ -0,0 +1,251 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import org.netbeans.api.progress.ProgressHandle; +import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent; +import org.sleuthkit.datamodel.SleuthkitJNI; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * The ImageWriter class is used to complete VHD copies created from local disks + * after the ingest process completes. + */ +class ImageWriter { + + private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); + + private final HashSet dataSourceIds = new HashSet<>(); + private final Object dataSourceIdsLock; // Get this lock before accessing dataSourceIds + + private final HashSet> progressUpdaters = new HashSet<>(); + private final HashSet imagesBeingFinished = new HashSet<>(); + private final HashSet progressBars = new HashSet<>(); + private final HashSet> finishTasksInProgress = new HashSet<>(); + private boolean isCancelled; + private final Object currentTasksLock; // Get this lock before accessing imagesBeingFinished, progressBars, progressUpdaters, finishTasksInProgress or isCancelled + + private boolean listenerStarted; + private ScheduledThreadPoolExecutor periodicTasksExecutor = null; + private final boolean doUI; + + ImageWriter(){ + dataSourceIdsLock = new Object(); + currentTasksLock = new Object(); + listenerStarted = false; + isCancelled = false; + + doUI = RuntimeProperties.coreComponentsAreActive(); + if(doUI){ + periodicTasksExecutor = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS + } + } + + /** + * Creates a listener on IngestJobEvents if it hasn't already been started. + * When a DataSourceAnalysisCompletedEvent arrives, if it matches + * the data source ID of an image that is using Image Writer, then finish the image + * (fill in any gaps). The AddImageTask for this data source must have included + * a non-empty imageWriterPath parameter to enable Image Writer. + */ + private synchronized void startListener(){ + if(! listenerStarted){ + IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ + DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; + + if(event.getDataSource() != null){ + long imageId = event.getDataSource().getId(); + String name = event.getDataSource().getName(); + + // Check whether we need to run finishImage for this data source + synchronized(dataSourceIdsLock){ + if( ! ImageWriter.this.dataSourceIds.contains(imageId)){ + // Image writer was not used on this data source or we've already finished it + return; + } else { + // Remove the imageId from the list here so we can't get past this point twice + // for the same image. Multiple DataSourceAnalysisCompletedEvent events can come from + // the same image if more ingest modules are run later, but the imageId is only added + // to the list during the intial task to add the image to the database. + ImageWriter.this.dataSourceIds.remove(imageId); + } + } + logger.log(Level.INFO, String.format("Finishing VHD image for %s", + event.getDataSource().getName())); //NON-NLS + + new Thread(() -> { + try{ + Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(imageId); + ProgressHandle progressHandle = null; + ScheduledFuture progressUpdateTask = null; + + if(doUI){ + progressHandle = ProgressHandle.createHandle("Image writer - " + name); + progressHandle.start(100); + progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( + new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); + } + + synchronized(currentTasksLock){ + ImageWriter.this.imagesBeingFinished.add(image.getImageHandle()); + + if(doUI){ + if(isCancelled){ + progressUpdateTask.cancel(true); + return; + } + ImageWriter.this.progressUpdaters.add(progressUpdateTask); + ImageWriter.this.progressBars.add(progressHandle); + } + } + + // The added complexity here with the Future is because we absolutely need to make sure + // the call to finishImageWriter returns before allowing the TSK data structures to be freed + // during case close. + Future finishTask = Executors.newSingleThreadExecutor().submit(() -> { + try{ + SleuthkitJNI.finishImageWriter(image.getImageHandle()); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }); + synchronized(currentTasksLock){ + ImageWriter.this.finishTasksInProgress.add(finishTask); + } + + // Wait for finishImageWriter to complete + try{ + // The call to get() will happen twice if the user closes the case, which is ok + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + + synchronized(currentTasksLock){ + ImageWriter.this.finishTasksInProgress.remove(finishTask); + ImageWriter.this.imagesBeingFinished.remove(image.getImageHandle()); + + if(doUI){ + progressUpdateTask.cancel(true); + ImageWriter.this.progressUpdaters.remove(progressUpdateTask); + progressHandle.finish(); + ImageWriter.this.progressBars.remove(progressHandle); + } + } + + logger.log(Level.INFO, String.format("Finished writing VHD image for %s", event.getDataSource().getName())); //NON-NLS + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }).start(); + } else { + logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS + } + } + } + }); + } + listenerStarted = true; + } + + /** + * Add a data source ID to the list of images to run finishImage on. + * Also starts the listener if needed. + * @param id The dataSource/Image ID + */ + void addDataSourceId(Long id){ + startListener(); + synchronized(dataSourceIdsLock){ + dataSourceIds.add(id); + } + } + + /** + * Stop any open progress update task, finish the progress bars, and tell + * the finishImage process to stop + */ + void close(){ + synchronized(currentTasksLock){ + isCancelled = true; + + for(ScheduledFuture task:ImageWriter.this.progressUpdaters){ + task.cancel(true); + } + + for(Long handle:imagesBeingFinished){ + SleuthkitJNI.cancelFinishImage(handle); + logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS + } + + // Wait for all the finish tasks to end + for(Future task:ImageWriter.this.finishTasksInProgress){ + try{ + task.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + } + + for(ProgressHandle progressHandle:ImageWriter.this.progressBars){ + progressHandle.finish(); + } + } + } + + /** + * Task to query the Sleuthkit processing to get the percentage done. + */ + private final class ProgressUpdateTask implements Runnable { + long imageHandle; + ProgressHandle progressHandle; + + ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle){ + this.imageHandle = imageHandle; + this.progressHandle = progressHandle; + } + + @Override + public void run() { + try { + int progress = SleuthkitJNI.getFinishImageProgress(imageHandle); + progressHandle.progress(progress); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Unexpected exception in ProgressUpdateTask", ex); //NON-NLS + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index e43c8b65c9..6162bc0061 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -46,6 +46,7 @@ + @@ -77,9 +78,11 @@ - + + + - + @@ -219,5 +222,12 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index ed53684b0c..e4f4763888 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -115,6 +115,7 @@ final class LocalDiskPanel extends JPanel { imageWriterPathLabel = new javax.swing.JLabel(); imageWriterPathDescLabel = new javax.swing.JLabel(); imageWriterErrorLabel = new javax.swing.JLabel(); + jLabel1 = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(485, 65)); @@ -151,6 +152,8 @@ final class LocalDiskPanel extends JPanel { imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(imageWriterErrorLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterErrorLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.jLabel1.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -174,7 +177,8 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterPathDescLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addGap(0, 0, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -199,9 +203,11 @@ final class LocalDiskPanel extends JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(imageWriterPathDescLabel) .addComponent(imageWriterPathLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(5, 5, 5) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(124, Short.MAX_VALUE)) + .addContainerGap(52, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables @@ -213,6 +219,7 @@ final class LocalDiskPanel extends JPanel { private javax.swing.JLabel imageWriterErrorLabel; private javax.swing.JLabel imageWriterPathDescLabel; private javax.swing.JLabel imageWriterPathLabel; + private javax.swing.JLabel jLabel1; private javax.swing.JCheckBox noFatOrphansCheckbox; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; From 2db6808410218d23c16725bc50c7f2333d573b01 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 2 Mar 2017 12:55:08 -0500 Subject: [PATCH 06/15] Add path selection for Image Writer to Local Disk panel --- .../autopsy/casemodule/Bundle.properties | 9 +- .../autopsy/casemodule/LocalDiskPanel.form | 101 +++++----- .../autopsy/casemodule/LocalDiskPanel.java | 176 ++++++++++++------ 3 files changed, 189 insertions(+), 97 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 12e2b15179..e25d76738d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -76,7 +76,10 @@ LocalDiskPanel.timeZoneLabel.text=Please select the input timezone: LocalDiskPanel.noFatOrphansCheckbox.toolTipText= LocalDiskPanel.noFatOrphansCheckbox.text=Ignore orphan files in FAT file systems LocalDiskPanel.descLabel.text=(faster results, although some data will not be searched) -LocalDiskPanel.imageWriterError.text=Error - file already exists +LocalDiskPanel.imageWriterDirError.text=Error - directory does not exist +LocalDiskPanel.imageWriterEmptyPathError.text=Error - enter path for VHD +LocalDiskPanel.imageWriterIsDirError.text=Error - VHD path is a directory +LocalDiskPanel.imageWriterFileExistsError.text=Error - VHD path already exists MissingImageDialog.browseButton.text=Browse MissingImageDialog.pathNameTextField.text= AddImageWizardAddingProgressVisual.progressTextArea.border.title=Status @@ -243,7 +246,7 @@ IngestJobInfoPanel.jLabel1.text=Ingest Modules IngestJobInfoPanel.jLabel2.text=Ingest Jobs CaseInformationPanel.closeButton.text=Close LocalDiskPanel.copyImageCheckbox.text=Make a VHD image of the drive while it is being analyzed -LocalDiskPanel.imageWriterPathLabel.text=jLabel1 -LocalDiskPanel.imageWriterPathDescLabel.text=Will be saved in the Module Output/Image Writer directory as : LocalDiskPanel.imageWriterErrorLabel.text=Error Label LocalDiskPanel.jLabel1.text=Note that at least one ingest module must be run to create a complete copy +LocalDiskPanel.pathTextField.text= +LocalDiskPanel.browseButton.text=Browse diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index 6162bc0061..89835246a9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -3,10 +3,10 @@
- + - + @@ -26,31 +26,38 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - + + + + + + + + + - + @@ -75,14 +82,14 @@ - - + + - + - + - + @@ -192,20 +199,9 @@ - - - - - - - - - - - - - - + + + @@ -229,5 +225,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index e4f4763888..2572c0c61d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -32,6 +32,7 @@ import java.util.TimeZone; import java.util.concurrent.CancellationException; import java.util.logging.Level; import javax.swing.ComboBoxModel; +import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; @@ -58,8 +59,8 @@ final class LocalDiskPanel extends JPanel { private static final long serialVersionUID = 1L; private List disks; private LocalDiskModel model; - private String fullImageWriterPath = ""; private boolean enableNext = false; + private final JFileChooser fc = new JFileChooser(); /** * Creates new form LocalDiskPanel @@ -91,8 +92,10 @@ final class LocalDiskPanel extends JPanel { errorLabel.setVisible(false); errorLabel.setText(""); diskComboBox.setEnabled(false); + imageWriterErrorLabel.setOpaque(true); imageWriterErrorLabel.setText(""); - imageWriterPathLabel.setText(""); + pathTextField.setEnabled(copyImageCheckbox.isSelected()); + browseButton.setEnabled(copyImageCheckbox.isSelected()); } /** @@ -112,13 +115,13 @@ final class LocalDiskPanel extends JPanel { noFatOrphansCheckbox = new javax.swing.JCheckBox(); descLabel = new javax.swing.JLabel(); copyImageCheckbox = new javax.swing.JCheckBox(); - imageWriterPathLabel = new javax.swing.JLabel(); - imageWriterPathDescLabel = new javax.swing.JLabel(); imageWriterErrorLabel = new javax.swing.JLabel(); jLabel1 = new javax.swing.JLabel(); + pathTextField = new javax.swing.JTextField(); + browseButton = new javax.swing.JButton(); - setMinimumSize(new java.awt.Dimension(0, 65)); - setPreferredSize(new java.awt.Dimension(485, 65)); + setMinimumSize(new java.awt.Dimension(0, 420)); + setPreferredSize(new java.awt.Dimension(485, 410)); diskLabel.setFont(diskLabel.getFont().deriveFont(diskLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(diskLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.diskLabel.text")); // NOI18N @@ -143,10 +146,11 @@ final class LocalDiskPanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.descLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(copyImageCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.copyImageCheckbox.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(imageWriterPathDescLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterPathDescLabel.text")); // NOI18N + copyImageCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + copyImageCheckboxActionPerformed(evt); + } + }); imageWriterErrorLabel.setFont(imageWriterErrorLabel.getFont().deriveFont(imageWriterErrorLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0)); @@ -154,32 +158,53 @@ final class LocalDiskPanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.jLabel1.text")); // NOI18N + pathTextField.setText(org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.pathTextField.text")); // NOI18N + pathTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + pathTextFieldKeyReleased(evt); + } + public void keyTyped(java.awt.event.KeyEvent evt) { + pathTextFieldKeyTyped(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(diskLabel) - .addComponent(diskComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 345, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(errorLabel) .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(noFatOrphansCheckbox) - .addComponent(copyImageCheckbox) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 362, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(diskLabel) + .addComponent(diskComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 345, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(errorLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(timeZoneLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(noFatOrphansCheckbox) + .addComponent(copyImageCheckbox) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(descLabel)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseButton)) .addGroup(layout.createSequentialGroup() .addGap(21, 21, 21) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(descLabel) - .addGroup(layout.createSequentialGroup() - .addComponent(imageWriterPathDescLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(imageWriterPathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 444, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(imageWriterErrorLabel)))) + .addGap(0, 29, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -201,30 +226,72 @@ final class LocalDiskPanel extends JPanel { .addComponent(copyImageCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(imageWriterPathDescLabel) - .addComponent(imageWriterPathLabel)) - .addGap(5, 5, 5) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(imageWriterErrorLabel) - .addContainerGap(52, Short.MAX_VALUE)) + .addContainerGap(170, Short.MAX_VALUE)) ); }// //GEN-END:initComponents + + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + String oldText = pathTextField.getText(); + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentFile = new File(oldText); + if ((currentFile.getParentFile() != null) && (currentFile.getParentFile().exists())) { + fc.setCurrentDirectory(currentFile.getParentFile()); + } + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathTextField.setText(path); + } + fireUpdateEvent(); + }//GEN-LAST:event_browseButtonActionPerformed + + private void copyImageCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyImageCheckboxActionPerformed + pathTextField.setEnabled(copyImageCheckbox.isSelected()); + browseButton.setEnabled(copyImageCheckbox.isSelected()); + fireUpdateEvent(); + }//GEN-LAST:event_copyImageCheckboxActionPerformed + + private void pathTextFieldKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_pathTextFieldKeyTyped + + }//GEN-LAST:event_pathTextFieldKeyTyped + + private void pathTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_pathTextFieldKeyReleased + fireUpdateEvent(); + }//GEN-LAST:event_pathTextFieldKeyReleased + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; private javax.swing.JCheckBox copyImageCheckbox; private javax.swing.JLabel descLabel; private javax.swing.JComboBox diskComboBox; private javax.swing.JLabel diskLabel; private javax.swing.JLabel errorLabel; private javax.swing.JLabel imageWriterErrorLabel; - private javax.swing.JLabel imageWriterPathDescLabel; - private javax.swing.JLabel imageWriterPathLabel; private javax.swing.JLabel jLabel1; private javax.swing.JCheckBox noFatOrphansCheckbox; + private javax.swing.JTextField pathTextField; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; // End of variables declaration//GEN-END:variables + private void fireUpdateEvent(){ + try { + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } catch (Exception e) { + logger.log(Level.SEVERE, "LocalDiskPanel listener threw exception", e); //NON-NLS + MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr"), + NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr.msg"), + MessageNotifyUtil.MessageType.ERROR); + } + } + /** * Return the currently selected disk path. * @@ -274,17 +341,30 @@ final class LocalDiskPanel extends JPanel { String path = disk.getName().replaceAll("[:]", ""); path += " " + System.currentTimeMillis(); path += ".vhd"; - imageWriterPathLabel.setText(path); - fullImageWriterPath = Paths.get(getDefaultImageWriterFolder(), path).toString(); + pathTextField.setText(Paths.get(getDefaultImageWriterFolder(), path).toString()); } private boolean imageWriterPathIsValid(){ - - File f = new File(fullImageWriterPath); - if(f.exists()) { - imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterError.text")); + if(pathTextField.getText().isEmpty()){ + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterEmptyPathError.text")); return false; } + + File f = new File(pathTextField.getText()); + if(((f.getParentFile() != null) && (! f.getParentFile().exists())) || + (f.getParentFile() == null)) { + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterDirError.text")); + return false; + } + if(f.isDirectory()){ + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterIsDirError.text")); + return false; + } + if(f.exists()){ + imageWriterErrorLabel.setText(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.imageWriterFileExistsError.text")); + return false; + } + imageWriterErrorLabel.setText(""); return true; } @@ -294,14 +374,14 @@ final class LocalDiskPanel extends JPanel { } String getImageWriterPath(){ - return fullImageWriterPath; + return pathTextField.getText(); } /** - * Should we enable the wizard's next button? Always return true because we - * control the possible selections. + * Should we enable the wizard's next button? We control all the possible + * selections except for Image Writer. * - * @return true + * @return true if panel is valid */ public boolean validatePanel() { if(copyImageCheckbox.isSelected() && @@ -406,15 +486,7 @@ final class LocalDiskPanel extends JPanel { selected = (LocalDisk) anItem; enableNext = true; setPotentialImageWriterPath((LocalDisk) selected); - - try { - firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); - } catch (Exception e) { - logger.log(Level.SEVERE, "LocalDiskPanel listener threw exception", e); //NON-NLS - MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr"), - NbBundle.getMessage(this.getClass(), "LocalDiskPanel.moduleErr.msg"), - MessageNotifyUtil.MessageType.ERROR); - } + fireUpdateEvent(); } } From 56781ac73adca856b4f1ad1e6bb555b3a67860ca Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 3 Mar 2017 14:12:19 -0500 Subject: [PATCH 07/15] Moved ImageWriter to the datasourceprocessors package. --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 1 + .../ImageWriter.java | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) rename Core/src/org/sleuthkit/autopsy/{casemodule => datasourceprocessors}/ImageWriter.java (98%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index f0c42d2fec..52166dab6a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; import java.awt.Cursor; import java.awt.Frame; import java.beans.PropertyChangeListener; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java rename to Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 6510c5231a..efddf47398 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; @@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.netbeans.api.progress.ProgressHandle; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Image; @@ -42,7 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException; * The ImageWriter class is used to complete VHD copies created from local disks * after the ingest process completes. */ -class ImageWriter { +public class ImageWriter { private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); @@ -60,7 +61,7 @@ class ImageWriter { private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - ImageWriter(){ + public ImageWriter(){ dataSourceIdsLock = new Object(); currentTasksLock = new Object(); listenerStarted = false; @@ -187,7 +188,7 @@ class ImageWriter { * Also starts the listener if needed. * @param id The dataSource/Image ID */ - void addDataSourceId(Long id){ + public void addDataSourceId(Long id){ startListener(); synchronized(dataSourceIdsLock){ dataSourceIds.add(id); @@ -198,7 +199,7 @@ class ImageWriter { * Stop any open progress update task, finish the progress bars, and tell * the finishImage process to stop */ - void close(){ + public void close(){ synchronized(currentTasksLock){ isCancelled = true; From 457dc82dd11f6ab1b5d7fd87d9983e28b6adec27 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 6 Mar 2017 13:57:36 -0500 Subject: [PATCH 08/15] Refactor image writer --- .../autopsy/casemodule/AddImageTask.java | 5 +- .../sleuthkit/autopsy/casemodule/Case.java | 18 -- .../datasourceprocessors/ImageWriter.java | 287 +++++++++--------- 3 files changed, 150 insertions(+), 160 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index cf7db4a392..58b4ada66f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; @@ -207,7 +208,9 @@ class AddImageTask implements Runnable { errorMessages.add(verificationError); } if(! imageWriterPath.isEmpty()){ - Case.getCurrentCase().scheduleImageWriterFinish(imageId); + // The ImageWriter object registers itself as an event listener and will + // stick around after this task is complete. + ImageWriter writer = new ImageWriter(imageId); } newDataSources.add(newImage); } else { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 52166dab6a..f27bd4e4f6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -287,7 +287,6 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60; private static final Logger logger = Logger.getLogger(Case.class.getName()); private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); - private final ImageWriter imageWriter = new ImageWriter(); private static String appName; private static Case currentCase; private static CoordinationService.Lock currentCaseLock; @@ -1439,7 +1438,6 @@ public class Case implements SleuthkitCase.ErrorObserver { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); - oldCase.closeImageWriter(); completeCaseChange(null); //closes windows, etc if (null != oldCase.tskErrorReporter) { oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case @@ -1616,22 +1614,6 @@ public class Case implements SleuthkitCase.ErrorObserver { } return currentCaseExecutor; } - - /** - * Register the ID of an image that is being copied using ImageWriter. - * This will cause the image to be finished after ingest is complete. - * @param imageID The image ID - */ - void scheduleImageWriterFinish(long imageID){ - imageWriter.addDataSourceId(imageID); - } - - /** - * Cancel all tasks associated with Image Writer - */ - void closeImageWriter(){ - imageWriter.close(); - } /** * Gets the time zone(s) of the image data source(s) in this case. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index efddf47398..13cfe70f0e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.HashSet; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -41,192 +40,198 @@ import org.sleuthkit.datamodel.TskCoreException; /** * The ImageWriter class is used to complete VHD copies created from local disks - * after the ingest process completes. + * after the ingest process completes. The AddImageTask for this data source must have included + * a non-empty imageWriterPath parameter to enable Image Writer. */ -public class ImageWriter { +public class ImageWriter implements PropertyChangeListener{ private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); - private final HashSet dataSourceIds = new HashSet<>(); - private final Object dataSourceIdsLock; // Get this lock before accessing dataSourceIds + private final Long dataSourceId; + private Long imageHandle = null; - private final HashSet> progressUpdaters = new HashSet<>(); - private final HashSet imagesBeingFinished = new HashSet<>(); - private final HashSet progressBars = new HashSet<>(); - private final HashSet> finishTasksInProgress = new HashSet<>(); + private Future finishTask; + ProgressHandle progressHandle = null; + ScheduledFuture progressUpdateTask = null; private boolean isCancelled; - private final Object currentTasksLock; // Get this lock before accessing imagesBeingFinished, progressBars, progressUpdaters, finishTasksInProgress or isCancelled + private boolean isStarted; + private boolean isFinished; + private final Object currentTasksLock; // Get this lock before accessing finishTask, progressHandle, progressUpdateTask, isCancelled, + // isStarted, or isFinished - private boolean listenerStarted; private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - public ImageWriter(){ - dataSourceIdsLock = new Object(); + private static int numFinishJobsInProgress = 0; + + public ImageWriter(Long dataSourceId){ + this.dataSourceId = dataSourceId; + currentTasksLock = new Object(); - listenerStarted = false; isCancelled = false; + isStarted = false; + isFinished = false; + progressHandle = null; + progressUpdateTask = null; + finishTask = null; doUI = RuntimeProperties.coreComponentsAreActive(); if(doUI){ - periodicTasksExecutor = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS + periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS } + + IngestManager.getInstance().addIngestJobEventListener(this); + Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } /** - * Creates a listener on IngestJobEvents if it hasn't already been started. - * When a DataSourceAnalysisCompletedEvent arrives, if it matches - * the data source ID of an image that is using Image Writer, then finish the image - * (fill in any gaps). The AddImageTask for this data source must have included - * a non-empty imageWriterPath parameter to enable Image Writer. + * Handle the events: + * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete + * CURRENT_CASE (case closing) - cancel the finish image process (if necessary) */ - private synchronized void startListener(){ - if(! listenerStarted){ - IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ - DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ + + DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; - if(event.getDataSource() != null){ - long imageId = event.getDataSource().getId(); - String name = event.getDataSource().getName(); - - // Check whether we need to run finishImage for this data source - synchronized(dataSourceIdsLock){ - if( ! ImageWriter.this.dataSourceIds.contains(imageId)){ - // Image writer was not used on this data source or we've already finished it - return; - } else { - // Remove the imageId from the list here so we can't get past this point twice - // for the same image. Multiple DataSourceAnalysisCompletedEvent events can come from - // the same image if more ingest modules are run later, but the imageId is only added - // to the list during the intial task to add the image to the database. - ImageWriter.this.dataSourceIds.remove(imageId); - } - } - logger.log(Level.INFO, String.format("Finishing VHD image for %s", - event.getDataSource().getName())); //NON-NLS - - new Thread(() -> { - try{ - Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(imageId); - ProgressHandle progressHandle = null; - ScheduledFuture progressUpdateTask = null; - - if(doUI){ - progressHandle = ProgressHandle.createHandle("Image writer - " + name); - progressHandle.start(100); - progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( - new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); - } - - synchronized(currentTasksLock){ - ImageWriter.this.imagesBeingFinished.add(image.getImageHandle()); - - if(doUI){ - if(isCancelled){ - progressUpdateTask.cancel(true); - return; - } - ImageWriter.this.progressUpdaters.add(progressUpdateTask); - ImageWriter.this.progressBars.add(progressHandle); - } - } - - // The added complexity here with the Future is because we absolutely need to make sure - // the call to finishImageWriter returns before allowing the TSK data structures to be freed - // during case close. - Future finishTask = Executors.newSingleThreadExecutor().submit(() -> { - try{ - SleuthkitJNI.finishImageWriter(image.getImageHandle()); - } catch (TskCoreException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - }); - synchronized(currentTasksLock){ - ImageWriter.this.finishTasksInProgress.add(finishTask); - } - - // Wait for finishImageWriter to complete - try{ - // The call to get() will happen twice if the user closes the case, which is ok - finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - - synchronized(currentTasksLock){ - ImageWriter.this.finishTasksInProgress.remove(finishTask); - ImageWriter.this.imagesBeingFinished.remove(image.getImageHandle()); - - if(doUI){ - progressUpdateTask.cancel(true); - ImageWriter.this.progressUpdaters.remove(progressUpdateTask); - progressHandle.finish(); - ImageWriter.this.progressBars.remove(progressHandle); - } - } - - logger.log(Level.INFO, String.format("Finished writing VHD image for %s", event.getDataSource().getName())); //NON-NLS - } catch (TskCoreException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - }).start(); - } else { - logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS - } - } + if(event.getDataSource() != null){ + long imageId = event.getDataSource().getId(); + String name = event.getDataSource().getName(); + + // Check that the event corresponds to this datasource + if(imageId != dataSourceId){ + return; } - }); + new Thread(() -> { + startFinishImage(name); + }).start(); + + } else { + logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS + } + } + else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){ + close(); } - listenerStarted = true; } - /** - * Add a data source ID to the list of images to run finishImage on. - * Also starts the listener if needed. - * @param id The dataSource/Image ID - */ - public void addDataSourceId(Long id){ - startListener(); - synchronized(dataSourceIdsLock){ - dataSourceIds.add(id); + private void startFinishImage(String dataSourceName){ + synchronized(currentTasksLock){ + // If we've already started the finish process for this datasource, return. + // Multiple DataSourceAnalysisCompletedEvent events can come from + // the same image if more ingest modules are run later + if(isStarted){ + return; + } else { + isStarted = true; + } + } + + logger.log(Level.INFO, String.format("Finishing VHD image for %s", + dataSourceName)); //NON-NLS + + try{ + Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); + imageHandle = image.getImageHandle(); + + synchronized(currentTasksLock){ + if(isCancelled){ + return; + } + + if(doUI){ + progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName); + progressHandle.start(100); + progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( + new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); + } + + // The added complexity here with the Future is because we absolutely need to make sure + // the call to finishImageWriter returns before allowing the TSK data structures to be freed + // during case close. + numFinishJobsInProgress++; + finishTask = Executors.newSingleThreadExecutor().submit(() -> { + try{ + SleuthkitJNI.finishImageWriter(imageHandle); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }); + } + + // Wait for finishImageWriter to complete + try{ + // The call to get() will happen twice if the user closes the case, which is ok + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + numFinishJobsInProgress--; + + IngestManager.getInstance().removeIngestJobEventListener(this); + Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); + synchronized(currentTasksLock){ + if(doUI && ! isCancelled){ + progressUpdateTask.cancel(true); + progressHandle.finish(); + } + isFinished = true; + } + + + logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS + } catch (TskCoreException | IllegalStateException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } } /** - * Stop any open progress update task, finish the progress bars, and tell - * the finishImage process to stop + * Tell the finishImage process to stop and wait for it to do so. */ - public void close(){ + private void close(){ synchronized(currentTasksLock){ isCancelled = true; - for(ScheduledFuture task:ImageWriter.this.progressUpdaters){ - task.cancel(true); + if(imageHandle == null){ + // The case got closed during ingest (before the finish process could start) + return; } - for(Long handle:imagesBeingFinished){ - SleuthkitJNI.cancelFinishImage(handle); + if(!isFinished){ + SleuthkitJNI.cancelFinishImage(imageHandle); logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS - } - // Wait for all the finish tasks to end - for(Future task:ImageWriter.this.finishTasksInProgress){ + // Wait for the finish task to end try{ - task.get(); + finishTask.get(); } catch (InterruptedException | ExecutionException ex){ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } - } - for(ProgressHandle progressHandle:ImageWriter.this.progressBars){ - progressHandle.finish(); + // Stop the progress bar and progress bar update task. + // The thread from startFinishImage will also stop these + // once the task completes, but we have to make absolutely sure + // this gets done before the Sleuthkit data structures are freed. + if(progressUpdateTask != null){ + progressUpdateTask.cancel(true); + } + + if(progressHandle != null){ + progressHandle.finish(); + } } } } + /** + * Get the number of images currently being finished. + * @return number of images in progress + */ + public static int numberOfJobsInProgress(){ + return numFinishJobsInProgress; + } + /** * Task to query the Sleuthkit processing to get the percentage done. */ From 507c8851c96add0db0b5ff5fafb3307ab46e267a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Mar 2017 08:32:42 -0500 Subject: [PATCH 09/15] Move event subscribers. Update to new GUI check. --- .../sleuthkit/autopsy/casemodule/AddImageTask.java | 4 ++-- .../autopsy/datasourceprocessors/ImageWriter.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 58b4ada66f..0e650a5f60 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; @@ -208,9 +209,8 @@ class AddImageTask implements Runnable { errorMessages.add(verificationError); } if(! imageWriterPath.isEmpty()){ - // The ImageWriter object registers itself as an event listener and will - // stick around after this task is complete. ImageWriter writer = new ImageWriter(imageId); + writer.subscribeToEvents(); } newDataSources.add(newImage); } else { diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 13cfe70f0e..0278c0895d 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -64,6 +64,11 @@ public class ImageWriter implements PropertyChangeListener{ private static int numFinishJobsInProgress = 0; + /** + * Create the Image Writer object. + * After creation, startListeners() should be called. + * @param dataSourceId + */ public ImageWriter(Long dataSourceId){ this.dataSourceId = dataSourceId; @@ -75,11 +80,16 @@ public class ImageWriter implements PropertyChangeListener{ progressUpdateTask = null; finishTask = null; - doUI = RuntimeProperties.coreComponentsAreActive(); + doUI = RuntimeProperties.runningWithGUI(); if(doUI){ periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS } - + } + + /** + * Add this ImageWriter object as a listener to the necessary events + */ + public void subscribeToEvents(){ IngestManager.getInstance().addIngestJobEventListener(this); Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } From 94dc472738cf5c9030af7bc59e67e9e62ddfaca0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 7 Mar 2017 14:47:36 -0500 Subject: [PATCH 10/15] Incorporating image writer feedback --- .../datasourceprocessors/ImageWriter.java | 187 +++++++++--------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 0278c0895d..7201344100 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -48,21 +49,21 @@ public class ImageWriter implements PropertyChangeListener{ private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); private final Long dataSourceId; - private Long imageHandle = null; - private Future finishTask; - ProgressHandle progressHandle = null; - ScheduledFuture progressUpdateTask = null; - private boolean isCancelled; - private boolean isStarted; - private boolean isFinished; - private final Object currentTasksLock; // Get this lock before accessing finishTask, progressHandle, progressUpdateTask, isCancelled, - // isStarted, or isFinished + private Long imageHandle = null; + private Future finishTask = null; + private ProgressHandle progressHandle = null; + private ScheduledFuture progressUpdateTask = null; + private boolean isCancelled = false; + private boolean isStarted = false; + private boolean isFinished = false; + private final Object currentTasksLock = new Object(); // Get this lock before accessing imageHandle, finishTask, progressHandle, progressUpdateTask, + // isCancelled, isStarted, or isFinished private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - private static int numFinishJobsInProgress = 0; + private static final AtomicInteger numFinishJobsInProgress = new AtomicInteger(0); /** * Create the Image Writer object. @@ -70,20 +71,8 @@ public class ImageWriter implements PropertyChangeListener{ * @param dataSourceId */ public ImageWriter(Long dataSourceId){ - this.dataSourceId = dataSourceId; - - currentTasksLock = new Object(); - isCancelled = false; - isStarted = false; - isFinished = false; - progressHandle = null; - progressUpdateTask = null; - finishTask = null; - - doUI = RuntimeProperties.runningWithGUI(); - if(doUI){ - periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS - } + this.dataSourceId = dataSourceId; + doUI = RuntimeProperties.runningWithGUI(); } /** @@ -94,6 +83,14 @@ public class ImageWriter implements PropertyChangeListener{ Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } + /** + * Deregister this object from the events. This is ok to call multiple times. + */ + public void unsubscribeFromEvents(){ + IngestManager.getInstance().removeIngestJobEventListener(this); + Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); + } + /** * Handle the events: * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete @@ -101,7 +98,7 @@ public class ImageWriter implements PropertyChangeListener{ */ @Override public void propertyChange(PropertyChangeEvent evt) { - if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){ + if(evt instanceof DataSourceAnalysisCompletedEvent){ DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt; @@ -122,12 +119,20 @@ public class ImageWriter implements PropertyChangeListener{ } } else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){ + // Technically this could be a case open event (and not the expected case close event) but + // that would probably mean something bad is going on, and we'd still want to cancel + // Image Writer. close(); } } private void startFinishImage(String dataSourceName){ + synchronized(currentTasksLock){ + if(isCancelled){ + return; + } + // If we've already started the finish process for this datasource, return. // Multiple DataSourceAnalysisCompletedEvent events can come from // the same image if more ingest modules are run later @@ -136,64 +141,63 @@ public class ImageWriter implements PropertyChangeListener{ } else { isStarted = true; } + + Image image; + try{ + image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); + imageHandle = image.getImageHandle(); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error loading image", ex); + + // Stay subscribed to the events for now. Case close will clean everything up. + return; + } + + logger.log(Level.INFO, String.format("Finishing VHD image for %s", + dataSourceName)); //NON-NLS + + if(doUI){ + periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS + progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName); + progressHandle.start(100); + progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( + new ProgressUpdateTask(progressHandle, imageHandle), 0, 250, TimeUnit.MILLISECONDS); + } + + // The added complexity here with the Future is because we absolutely need to make sure + // the call to finishImageWriter returns before allowing the TSK data structures to be freed + // during case close. + numFinishJobsInProgress.incrementAndGet(); + finishTask = Executors.newSingleThreadExecutor().submit(() -> { + try{ + SleuthkitJNI.finishImageWriter(imageHandle); + } catch (TskCoreException ex){ + logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + }); } - logger.log(Level.INFO, String.format("Finishing VHD image for %s", - dataSourceName)); //NON-NLS - + // Wait for finishImageWriter to complete try{ - Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); - imageHandle = image.getImageHandle(); - - synchronized(currentTasksLock){ - if(isCancelled){ - return; - } - - if(doUI){ - progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName); - progressHandle.start(100); - progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate( - new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS); - } - - // The added complexity here with the Future is because we absolutely need to make sure - // the call to finishImageWriter returns before allowing the TSK data structures to be freed - // during case close. - numFinishJobsInProgress++; - finishTask = Executors.newSingleThreadExecutor().submit(() -> { - try{ - SleuthkitJNI.finishImageWriter(imageHandle); - } catch (TskCoreException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - }); - } - - // Wait for finishImageWriter to complete - try{ - // The call to get() will happen twice if the user closes the case, which is ok - finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - numFinishJobsInProgress--; - - IngestManager.getInstance().removeIngestJobEventListener(this); - Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); - synchronized(currentTasksLock){ - if(doUI && ! isCancelled){ - progressUpdateTask.cancel(true); - progressHandle.finish(); - } - isFinished = true; - } - - - logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS - } catch (TskCoreException | IllegalStateException ex){ + // The call to get() will happen twice if the user closes the case, which is ok + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } + numFinishJobsInProgress.decrementAndGet(); + + synchronized(currentTasksLock){ + unsubscribeFromEvents(); + if(doUI){ + // Some of these may be called twice if the user closes the case + progressUpdateTask.cancel(true); + progressHandle.finish(); + periodicTasksExecutor.shutdown(); + } + isFinished = true; + } + + logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS } /** @@ -203,8 +207,12 @@ public class ImageWriter implements PropertyChangeListener{ synchronized(currentTasksLock){ isCancelled = true; + unsubscribeFromEvents(); + + // The case either got closed during ingest (before the startFinishImage got called) + // or an error occurred. If imageHandle is not null, we know that the first block of + // startFinishImage() completed so all the tasks have also been initialized. if(imageHandle == null){ - // The case got closed during ingest (before the finish process could start) return; } @@ -219,17 +227,14 @@ public class ImageWriter implements PropertyChangeListener{ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } - // Stop the progress bar and progress bar update task. - // The thread from startFinishImage will also stop these + // Stop the progress bar update task. + // The thread from startFinishImage will also stop it // once the task completes, but we have to make absolutely sure // this gets done before the Sleuthkit data structures are freed. - if(progressUpdateTask != null){ - progressUpdateTask.cancel(true); - } - - if(progressHandle != null){ - progressHandle.finish(); - } + // Since we've stopped the update task, we'll stop the associated progress + // bar now, too. + progressUpdateTask.cancel(true); + progressHandle.finish(); } } } @@ -239,15 +244,15 @@ public class ImageWriter implements PropertyChangeListener{ * @return number of images in progress */ public static int numberOfJobsInProgress(){ - return numFinishJobsInProgress; + return numFinishJobsInProgress.get(); } /** * Task to query the Sleuthkit processing to get the percentage done. */ private final class ProgressUpdateTask implements Runnable { - long imageHandle; - ProgressHandle progressHandle; + final long imageHandle; + final ProgressHandle progressHandle; ProgressUpdateTask(ProgressHandle progressHandle, long imageHandle){ this.imageHandle = imageHandle; From 980efcb32674876141550c2fca759b2650fcb7b6 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 9 Mar 2017 13:45:14 -0500 Subject: [PATCH 11/15] Adding ImageWriterService --- .../datasourceprocessors/Bundle.properties | 1 + .../datasourceprocessors/ImageWriter.java | 150 +++++++++++++----- .../ImageWriterService.java | 60 +++++++ 3 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties index 4a8de9a48b..079c93d832 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties @@ -10,3 +10,4 @@ RawDSInputPanel.jBreakFileUpLabel.text=Break image up into: RawDSInputPanel.jNoBreakupRadioButton.text=Do not break up RawDSInputPanel.j2GBBreakupRadioButton.text=2GB chunks RawDSInputPanel.timeZoneLabel.text=Please select the input timezone: +ImageWriterService.serviceName=Image Writer \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 7201344100..9337aadd1b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -21,7 +21,8 @@ package org.sleuthkit.autopsy.datasourceprocessors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -43,6 +44,8 @@ import org.sleuthkit.datamodel.TskCoreException; * The ImageWriter class is used to complete VHD copies created from local disks * after the ingest process completes. The AddImageTask for this data source must have included * a non-empty imageWriterPath parameter to enable Image Writer. + * + * Most of the cancellation/cleanup is handled through ImageWriterService */ public class ImageWriter implements PropertyChangeListener{ @@ -56,14 +59,16 @@ public class ImageWriter implements PropertyChangeListener{ private ScheduledFuture progressUpdateTask = null; private boolean isCancelled = false; private boolean isStarted = false; - private boolean isFinished = false; private final Object currentTasksLock = new Object(); // Get this lock before accessing imageHandle, finishTask, progressHandle, progressUpdateTask, // isCancelled, isStarted, or isFinished private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - private static final AtomicInteger numFinishJobsInProgress = new AtomicInteger(0); + private static final ImageWriterService service = new ImageWriterService(); + + private static final Set currentImageWriters = new HashSet<>(); // Contains all Image Writer objects that could be processing + private static final Object currentImageWritersLock = new Object(); // Get this lock before accessing currentImageWriters /** * Create the Image Writer object. @@ -94,7 +99,7 @@ public class ImageWriter implements PropertyChangeListener{ /** * Handle the events: * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete - * CURRENT_CASE (case closing) - cancel the finish image process (if necessary) + * CURRENT_CASE (case closing) - do some cleanup */ @Override public void propertyChange(PropertyChangeEvent evt) { @@ -120,14 +125,14 @@ public class ImageWriter implements PropertyChangeListener{ } else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){ // Technically this could be a case open event (and not the expected case close event) but - // that would probably mean something bad is going on, and we'd still want to cancel - // Image Writer. + // that would probably mean something bad is going on, and we'd still want to + // do the cleanup close(); } } private void startFinishImage(String dataSourceName){ - + synchronized(currentTasksLock){ if(isCancelled){ return; @@ -141,6 +146,18 @@ public class ImageWriter implements PropertyChangeListener{ } else { isStarted = true; } + } + + // Add this to the list of image writers that could be in progress + // (this could get cancelled before the task is created) + synchronized(currentImageWritersLock){ + currentImageWriters.add(this); + } + + synchronized(currentTasksLock){ + if(isCancelled){ + return; + } Image image; try{ @@ -150,6 +167,7 @@ public class ImageWriter implements PropertyChangeListener{ logger.log(Level.SEVERE, "Error loading image", ex); // Stay subscribed to the events for now. Case close will clean everything up. + imageHandle = null; return; } @@ -167,7 +185,6 @@ public class ImageWriter implements PropertyChangeListener{ // The added complexity here with the Future is because we absolutely need to make sure // the call to finishImageWriter returns before allowing the TSK data structures to be freed // during case close. - numFinishJobsInProgress.incrementAndGet(); finishTask = Executors.newSingleThreadExecutor().submit(() -> { try{ SleuthkitJNI.finishImageWriter(imageHandle); @@ -179,13 +196,12 @@ public class ImageWriter implements PropertyChangeListener{ // Wait for finishImageWriter to complete try{ - // The call to get() will happen twice if the user closes the case, which is ok + // The call to get() can happen multiple times if the user closes the case, which is ok finishTask.get(); } catch (InterruptedException | ExecutionException ex){ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } - numFinishJobsInProgress.decrementAndGet(); - + synchronized(currentTasksLock){ unsubscribeFromEvents(); if(doUI){ @@ -193,40 +209,59 @@ public class ImageWriter implements PropertyChangeListener{ progressUpdateTask.cancel(true); progressHandle.finish(); periodicTasksExecutor.shutdown(); - } - isFinished = true; + } + } + + synchronized(currentImageWritersLock){ + currentImageWriters.remove(this); } logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS } /** - * Tell the finishImage process to stop and wait for it to do so. + * Check if any finish image tasks are in progress. + * @return true if there are finish image tasks in progress, false otherwise */ - private void close(){ - synchronized(currentTasksLock){ - isCancelled = true; - - unsubscribeFromEvents(); - - // The case either got closed during ingest (before the startFinishImage got called) - // or an error occurred. If imageHandle is not null, we know that the first block of - // startFinishImage() completed so all the tasks have also been initialized. - if(imageHandle == null){ - return; - } - - if(!isFinished){ - SleuthkitJNI.cancelFinishImage(imageHandle); - logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS - - // Wait for the finish task to end - try{ - finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + public static boolean jobsAreInProgress(){ + synchronized(currentImageWritersLock){ + for(ImageWriter writer:currentImageWriters){ + if((writer.finishTask != null) && (! writer.finishTask.isDone())){ + return true; } - + } + } + + return false; + } + + /** + * Cancels all Image Writer jobs in progress. + * Does not wait for them to finish. It does stop the progress update task, + * so after this point all that needs to be tested is that the + * finishTask Futures are done. + */ + public static void cancelAllJobs(){ + synchronized(currentImageWritersLock){ + for(ImageWriter writer:currentImageWriters){ + writer.cancelJob(); + } + } + } + + /** + * Cancels a single job. + * Does not wait for the job to complete. + */ + private void cancelJob(){ + synchronized(currentTasksLock){ + // All of the following is redundant but safe to call on a complete job + isCancelled = true; + unsubscribeFromEvents(); + + if(imageHandle != null){ + SleuthkitJNI.cancelFinishImage(imageHandle); + // Stop the progress bar update task. // The thread from startFinishImage will also stop it // once the task completes, but we have to make absolutely sure @@ -235,18 +270,49 @@ public class ImageWriter implements PropertyChangeListener{ // bar now, too. progressUpdateTask.cancel(true); progressHandle.finish(); - } + } } } /** - * Get the number of images currently being finished. - * @return number of images in progress + * Blocks while all finishImage tasks complete. + * Also makes sure the progressUpdateTask is canceled. + * Once this is done it will be safe to release the Sleuthkit resources. */ - public static int numberOfJobsInProgress(){ - return numFinishJobsInProgress.get(); + public static void waitForAllJobsToFinish(){ + synchronized(currentImageWritersLock){ + for(ImageWriter writer:currentImageWriters){ + // Wait for the finish task to end + if(writer.finishTask != null){ + try{ + writer.finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + writer.progressUpdateTask.cancel(true); + } + } + } } + /** + * Clean up any Image Writer objects that haven't started yet. + * This is just protecting against the unlikely event that, due to timing + * issues, an Image Writer object will be trying to start up while the case is + * closing. + */ + private void close(){ + + synchronized(currentTasksLock){ + if(imageHandle == null){ + isCancelled = true; // Prevent the task from starting in case something strange is happening + // with the event order + this.unsubscribeFromEvents(); + } + } + } + + /** * Task to query the Sleuthkit processing to get the percentage done. */ diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java new file mode 100644 index 0000000000..84c8d157c0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java @@ -0,0 +1,60 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourceprocessors; + +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.framework.AutopsyService; + +@ServiceProviders(value = {@ServiceProvider(service = AutopsyService.class)}) + +public class ImageWriterService implements AutopsyService { + + @Override + public String getServiceName() { + return NbBundle.getMessage(this.getClass(), "ImageWriterService.serviceName"); + } + + @Override + public void closeCaseResources(CaseContext context) throws AutopsyServiceException { + context.getProgressIndicator().progress("Waiting for VHD(s) to complete"); + + if(ImageWriter.jobsAreInProgress()){ + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + "Wait for Image Writer to finish?", + "Title", + NotifyDescriptor.YES_NO_OPTION, + NotifyDescriptor.WARNING_MESSAGE); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + + if(response == DialogDescriptor.NO_OPTION){ + ImageWriter.cancelAllJobs(); + } + + // Wait for all finishImage jobs to complete. If the jobs got cancelled + // this will be very fast. + ImageWriter.waitForAllJobsToFinish(); + } + } +} From d12c5b48a8e6f5dde540c2d9bf6d2c0ca38221c2 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 9 Mar 2017 15:00:24 -0500 Subject: [PATCH 12/15] More refactoring --- .../autopsy/casemodule/AddImageTask.java | 5 +- .../datasourceprocessors/ImageWriter.java | 119 +++++------------- .../ImageWriterService.java | 72 +++++++++-- 3 files changed, 93 insertions(+), 103 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 0e650a5f60..533e299de2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -26,7 +26,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter; +import org.sleuthkit.autopsy.datasourceprocessors.ImageWriterService; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; @@ -209,8 +209,7 @@ class AddImageTask implements Runnable { errorMessages.add(verificationError); } if(! imageWriterPath.isEmpty()){ - ImageWriter writer = new ImageWriter(imageId); - writer.subscribeToEvents(); + ImageWriterService.createImageWriter(imageId); } newDataSources.add(newImage); } else { diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java index 9337aadd1b..71e9c9f440 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java @@ -47,7 +47,7 @@ import org.sleuthkit.datamodel.TskCoreException; * * Most of the cancellation/cleanup is handled through ImageWriterService */ -public class ImageWriter implements PropertyChangeListener{ +class ImageWriter implements PropertyChangeListener{ private final Logger logger = Logger.getLogger(ImageWriter.class.getName()); @@ -67,15 +67,14 @@ public class ImageWriter implements PropertyChangeListener{ private static final ImageWriterService service = new ImageWriterService(); - private static final Set currentImageWriters = new HashSet<>(); // Contains all Image Writer objects that could be processing - private static final Object currentImageWritersLock = new Object(); // Get this lock before accessing currentImageWriters + /** * Create the Image Writer object. * After creation, startListeners() should be called. * @param dataSourceId */ - public ImageWriter(Long dataSourceId){ + ImageWriter(Long dataSourceId){ this.dataSourceId = dataSourceId; doUI = RuntimeProperties.runningWithGUI(); } @@ -83,23 +82,20 @@ public class ImageWriter implements PropertyChangeListener{ /** * Add this ImageWriter object as a listener to the necessary events */ - public void subscribeToEvents(){ + void subscribeToEvents(){ IngestManager.getInstance().addIngestJobEventListener(this); - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); } /** * Deregister this object from the events. This is ok to call multiple times. */ - public void unsubscribeFromEvents(){ - IngestManager.getInstance().removeIngestJobEventListener(this); - Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this); + void unsubscribeFromEvents(){ + IngestManager.getInstance().removeIngestJobEventListener(this); } /** * Handle the events: * DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete - * CURRENT_CASE (case closing) - do some cleanup */ @Override public void propertyChange(PropertyChangeEvent evt) { @@ -123,12 +119,6 @@ public class ImageWriter implements PropertyChangeListener{ logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS } } - else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){ - // Technically this could be a case open event (and not the expected case close event) but - // that would probably mean something bad is going on, and we'd still want to - // do the cleanup - close(); - } } private void startFinishImage(String dataSourceName){ @@ -146,18 +136,6 @@ public class ImageWriter implements PropertyChangeListener{ } else { isStarted = true; } - } - - // Add this to the list of image writers that could be in progress - // (this could get cancelled before the task is created) - synchronized(currentImageWritersLock){ - currentImageWriters.add(this); - } - - synchronized(currentTasksLock){ - if(isCancelled){ - return; - } Image image; try{ @@ -211,49 +189,37 @@ public class ImageWriter implements PropertyChangeListener{ periodicTasksExecutor.shutdown(); } } - - synchronized(currentImageWritersLock){ - currentImageWriters.remove(this); - } logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS } /** - * Check if any finish image tasks are in progress. - * @return true if there are finish image tasks in progress, false otherwise + * If a task hasn't been started yet, set the cancel flag so it can no longer + * start. */ - public static boolean jobsAreInProgress(){ - synchronized(currentImageWritersLock){ - for(ImageWriter writer:currentImageWriters){ - if((writer.finishTask != null) && (! writer.finishTask.isDone())){ - return true; - } + void cancelIfNotStarted(){ + synchronized(currentTasksLock){ + if(finishTask == null){ + isCancelled = true; } + unsubscribeFromEvents(); } - - return false; } /** - * Cancels all Image Writer jobs in progress. - * Does not wait for them to finish. It does stop the progress update task, - * so after this point all that needs to be tested is that the - * finishTask Futures are done. + * Check if the finishTask process is running. + * @return true if the finish task is still going on, false if it is finished or + * never started */ - public static void cancelAllJobs(){ - synchronized(currentImageWritersLock){ - for(ImageWriter writer:currentImageWriters){ - writer.cancelJob(); - } - } + boolean jobIsInProgress(){ + return((finishTask != null) && (! finishTask.isDone())); } /** * Cancels a single job. - * Does not wait for the job to complete. + * Does not wait for the job to complete. Safe to call with Image Writer in any state. */ - private void cancelJob(){ + void cancelJob(){ synchronized(currentTasksLock){ // All of the following is redundant but safe to call on a complete job isCancelled = true; @@ -264,8 +230,8 @@ public class ImageWriter implements PropertyChangeListener{ // Stop the progress bar update task. // The thread from startFinishImage will also stop it - // once the task completes, but we have to make absolutely sure - // this gets done before the Sleuthkit data structures are freed. + // once the task completes, but we don't have a guarantee on + // when that happens. // Since we've stopped the update task, we'll stop the associated progress // bar now, too. progressUpdateTask.cancel(true); @@ -277,42 +243,19 @@ public class ImageWriter implements PropertyChangeListener{ /** * Blocks while all finishImage tasks complete. * Also makes sure the progressUpdateTask is canceled. - * Once this is done it will be safe to release the Sleuthkit resources. */ - public static void waitForAllJobsToFinish(){ - synchronized(currentImageWritersLock){ - for(ImageWriter writer:currentImageWriters){ - // Wait for the finish task to end - if(writer.finishTask != null){ - try{ - writer.finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - writer.progressUpdateTask.cancel(true); - } + void waitForJobToFinish(){ + // Wait for the finish task to end + if(finishTask != null){ + try{ + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } - } + progressUpdateTask.cancel(true); + } } - /** - * Clean up any Image Writer objects that haven't started yet. - * This is just protecting against the unlikely event that, due to timing - * issues, an Image Writer object will be trying to start up while the case is - * closing. - */ - private void close(){ - - synchronized(currentTasksLock){ - if(imageHandle == null){ - isCancelled = true; // Prevent the task from starting in case something strange is happening - // with the event order - this.unsubscribeFromEvents(); - } - } - } - - /** * Task to query the Sleuthkit processing to get the percentage done. */ diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java index 84c8d157c0..e6211b748a 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datasourceprocessors; +import java.util.HashSet; +import java.util.Set; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -30,6 +32,25 @@ import org.sleuthkit.autopsy.framework.AutopsyService; public class ImageWriterService implements AutopsyService { + private static final Set imageWriters = new HashSet<>(); // Contains all Image Writer objects + private static final Object imageWritersLock = new Object(); // Get this lock before accessing currentImageWriters + + /** + * Create an image writer object for the given data source ID. + * @param imageId ID for the image + */ + public static void createImageWriter(Long imageId){ + + // ImageWriter objects are created during the addImageTask. They can not arrive while + // we're closing case resources so we don't need to worry about one showing up while + // doing our close/cleanup. + synchronized(imageWritersLock){ + ImageWriter writer = new ImageWriter(imageId); + writer.subscribeToEvents(); + imageWriters.add(writer); + } + } + @Override public String getServiceName() { return NbBundle.getMessage(this.getClass(), "ImageWriterService.serviceName"); @@ -38,23 +59,50 @@ public class ImageWriterService implements AutopsyService { @Override public void closeCaseResources(CaseContext context) throws AutopsyServiceException { context.getProgressIndicator().progress("Waiting for VHD(s) to complete"); - - if(ImageWriter.jobsAreInProgress()){ - NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( + + synchronized(imageWritersLock){ + // If any of our ImageWriter objects haven't started the finish task, set the cancel flag + // to make sure they don't start now. The reason they haven't started is that + // ingest was not complete, and the user already confirmed that they want to exit + // even though ingest is not complete so we will take that to mean that they + // also don't want to wait for Image Writer. + for(ImageWriter writer: imageWriters){ + writer.cancelIfNotStarted(); + } + + // Test whether any finishImage tasks are in progress + boolean jobsAreInProgress = false; + for(ImageWriter writer: imageWriters){ + if(writer.jobIsInProgress()){ + jobsAreInProgress = true; + break; + } + } + + if(jobsAreInProgress){ + // If jobs are in progress, ask the user if they want to wait for them to complete + NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( "Wait for Image Writer to finish?", "Title", NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); - descriptor.setValue(NotifyDescriptor.NO_OPTION); - Object response = DialogDisplayer.getDefault().notify(descriptor); - - if(response == DialogDescriptor.NO_OPTION){ - ImageWriter.cancelAllJobs(); + descriptor.setValue(NotifyDescriptor.NO_OPTION); + Object response = DialogDisplayer.getDefault().notify(descriptor); + + if(response == DialogDescriptor.NO_OPTION){ + // Cancel all the jobs + for(ImageWriter writer: imageWriters){ + writer.cancelJob(); + } + } + + // Wait for all finishImage jobs to complete. If the jobs got cancelled + // this will be very fast. + for(ImageWriter writer: imageWriters){ + writer.waitForJobToFinish(); + } + } - - // Wait for all finishImage jobs to complete. If the jobs got cancelled - // this will be very fast. - ImageWriter.waitForAllJobsToFinish(); } } } From 7173bd55bb072c25af3ed634f8f788968b80e5c9 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 10 Mar 2017 09:57:35 -0500 Subject: [PATCH 13/15] More refactoring and cleanup. Updated strings in confirmation dialog. Moved image writer to new package --- .../autopsy/casemodule/AddImageTask.java | 2 +- .../datasourceprocessors/Bundle.properties | 5 +- .../ImageWriter.java | 56 +++++++++---------- .../ImageWriterService.java | 25 +++++++-- 4 files changed, 53 insertions(+), 35 deletions(-) rename Core/src/org/sleuthkit/autopsy/{datasourceprocessors => imagewriter}/ImageWriter.java (89%) rename Core/src/org/sleuthkit/autopsy/{datasourceprocessors => imagewriter}/ImageWriterService.java (82%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 533e299de2..5ad9a606a6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -26,7 +26,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datasourceprocessors.ImageWriterService; +import org.sleuthkit.autopsy.imagewriter.ImageWriterService; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties index 079c93d832..4b55829ba6 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties @@ -10,4 +10,7 @@ RawDSInputPanel.jBreakFileUpLabel.text=Break image up into: RawDSInputPanel.jNoBreakupRadioButton.text=Do not break up RawDSInputPanel.j2GBBreakupRadioButton.text=2GB chunks RawDSInputPanel.timeZoneLabel.text=Please select the input timezone: -ImageWriterService.serviceName=Image Writer \ No newline at end of file +ImageWriterService.serviceName=Image Writer +ImageWriterService.waitingForVHDs=Waiting for VHD(s) to complete +ImageWriterService.shouldWait=Wait for VHD(s) in progress to complete? +ImageWriterService.localDisk=Local disk image copy diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java similarity index 89% rename from Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java rename to Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java index 71e9c9f440..80f7fa8aa4 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java @@ -16,13 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datasourceprocessors; +package org.sleuthkit.autopsy.imagewriter; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.HashSet; -import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -65,10 +63,6 @@ class ImageWriter implements PropertyChangeListener{ private ScheduledThreadPoolExecutor periodicTasksExecutor = null; private final boolean doUI; - private static final ImageWriterService service = new ImageWriterService(); - - - /** * Create the Image Writer object. * After creation, startListeners() should be called. @@ -133,8 +127,6 @@ class ImageWriter implements PropertyChangeListener{ // the same image if more ingest modules are run later if(isStarted){ return; - } else { - isStarted = true; } Image image; @@ -143,8 +135,6 @@ class ImageWriter implements PropertyChangeListener{ imageHandle = image.getImageHandle(); } catch (TskCoreException ex){ logger.log(Level.SEVERE, "Error loading image", ex); - - // Stay subscribed to the events for now. Case close will clean everything up. imageHandle = null; return; } @@ -170,6 +160,9 @@ class ImageWriter implements PropertyChangeListener{ logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS } }); + + // Setting this means that finishTask and all the UI updaters are initialized (if running UI) + isStarted = true; } // Wait for finishImageWriter to complete @@ -181,7 +174,6 @@ class ImageWriter implements PropertyChangeListener{ } synchronized(currentTasksLock){ - unsubscribeFromEvents(); if(doUI){ // Some of these may be called twice if the user closes the case progressUpdateTask.cancel(true); @@ -196,13 +188,14 @@ class ImageWriter implements PropertyChangeListener{ /** * If a task hasn't been started yet, set the cancel flag so it can no longer * start. + * This is intended to be used in case close so a job doesn't suddenly start + * up during cleanup. */ void cancelIfNotStarted(){ synchronized(currentTasksLock){ - if(finishTask == null){ + if(! isStarted){ isCancelled = true; } - unsubscribeFromEvents(); } } @@ -212,7 +205,9 @@ class ImageWriter implements PropertyChangeListener{ * never started */ boolean jobIsInProgress(){ - return((finishTask != null) && (! finishTask.isDone())); + synchronized(currentTasksLock){ + return((isStarted) && (! finishTask.isDone())); + } } /** @@ -223,9 +218,8 @@ class ImageWriter implements PropertyChangeListener{ synchronized(currentTasksLock){ // All of the following is redundant but safe to call on a complete job isCancelled = true; - unsubscribeFromEvents(); - if(imageHandle != null){ + if(isStarted){ SleuthkitJNI.cancelFinishImage(imageHandle); // Stop the progress bar update task. @@ -234,8 +228,10 @@ class ImageWriter implements PropertyChangeListener{ // when that happens. // Since we've stopped the update task, we'll stop the associated progress // bar now, too. - progressUpdateTask.cancel(true); - progressHandle.finish(); + if(doUI){ + progressUpdateTask.cancel(true); + progressHandle.finish(); + } } } } @@ -245,15 +241,19 @@ class ImageWriter implements PropertyChangeListener{ * Also makes sure the progressUpdateTask is canceled. */ void waitForJobToFinish(){ - // Wait for the finish task to end - if(finishTask != null){ - try{ - finishTask.get(); - } catch (InterruptedException | ExecutionException ex){ - Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS - } - progressUpdateTask.cancel(true); - } + synchronized(currentTasksLock){ + // Wait for the finish task to end + if(isStarted){ + try{ + finishTask.get(); + } catch (InterruptedException | ExecutionException ex){ + Logger.getLogger(ImageWriter.class.getName()).log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS + } + if(doUI){ + progressUpdateTask.cancel(true); + } + } + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java similarity index 82% rename from Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java rename to Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java index e6211b748a..8f3791d53c 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/ImageWriterService.java +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriterService.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datasourceprocessors; +package org.sleuthkit.autopsy.imagewriter; import java.util.HashSet; import java.util.Set; @@ -30,6 +30,13 @@ import org.sleuthkit.autopsy.framework.AutopsyService; @ServiceProviders(value = {@ServiceProvider(service = AutopsyService.class)}) +/** + * Creates and handles closing of ImageWriter objects. + * Currently, ImageWriter is only enabled for local disks, and local disks can + * not be processed in multi user mode. If ImageWriter is ever enabled for multi user + * cases this code will need to be revised. + */ + public class ImageWriterService implements AutopsyService { private static final Set imageWriters = new HashSet<>(); // Contains all Image Writer objects @@ -58,8 +65,8 @@ public class ImageWriterService implements AutopsyService { @Override public void closeCaseResources(CaseContext context) throws AutopsyServiceException { - context.getProgressIndicator().progress("Waiting for VHD(s) to complete"); - + context.getProgressIndicator().progress(NbBundle.getMessage(this.getClass(), "ImageWriterService.waitingForVHDs")); + synchronized(imageWritersLock){ // If any of our ImageWriter objects haven't started the finish task, set the cancel flag // to make sure they don't start now. The reason they haven't started is that @@ -82,8 +89,8 @@ public class ImageWriterService implements AutopsyService { if(jobsAreInProgress){ // If jobs are in progress, ask the user if they want to wait for them to complete NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation( - "Wait for Image Writer to finish?", - "Title", + NbBundle.getMessage(this.getClass(), "ImageWriterService.shouldWait"), + NbBundle.getMessage(this.getClass(), "ImageWriterService.localDisk"), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE); descriptor.setValue(NotifyDescriptor.NO_OPTION); @@ -103,6 +110,14 @@ public class ImageWriterService implements AutopsyService { } } + + // Stop listening for events + for(ImageWriter writer: imageWriters){ + writer.unsubscribeFromEvents(); + } + + // Clear out the list of Image Writers + imageWriters.clear(); } } } From bddd918d832c689751db09c60c88a77abc8b34dc Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 10 Mar 2017 10:23:35 -0500 Subject: [PATCH 14/15] Forgot to make a new Bundle.properties file --- .../autopsy/datasourceprocessors/Bundle.properties | 5 +---- .../org/sleuthkit/autopsy/imagewriter/Bundle.properties | 8 ++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/imagewriter/Bundle.properties diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties index 4b55829ba6..ad62c83c2f 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/Bundle.properties @@ -10,7 +10,4 @@ RawDSInputPanel.jBreakFileUpLabel.text=Break image up into: RawDSInputPanel.jNoBreakupRadioButton.text=Do not break up RawDSInputPanel.j2GBBreakupRadioButton.text=2GB chunks RawDSInputPanel.timeZoneLabel.text=Please select the input timezone: -ImageWriterService.serviceName=Image Writer -ImageWriterService.waitingForVHDs=Waiting for VHD(s) to complete -ImageWriterService.shouldWait=Wait for VHD(s) in progress to complete? -ImageWriterService.localDisk=Local disk image copy + diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/Bundle.properties b/Core/src/org/sleuthkit/autopsy/imagewriter/Bundle.properties new file mode 100644 index 0000000000..67a92df30f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/Bundle.properties @@ -0,0 +1,8 @@ +# To change this license header, choose License Headers in Project Properties. +# To change this template file, choose Tools | Templates +# and open the template in the editor. + +ImageWriterService.serviceName=Image Writer +ImageWriterService.waitingForVHDs=Waiting for VHD(s) to complete +ImageWriterService.shouldWait=Wait for VHD(s) in progress to complete? +ImageWriterService.localDisk=Local disk image copy From 969828236eae37ad7d4ff6bf05babae0fcebc812 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 10 Mar 2017 11:18:26 -0500 Subject: [PATCH 15/15] Added IllegalStateException case --- .../org/sleuthkit/autopsy/imagewriter/ImageWriter.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java index 80f7fa8aa4..55bfa23326 100644 --- a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java @@ -133,9 +133,16 @@ class ImageWriter implements PropertyChangeListener{ try{ image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId); imageHandle = image.getImageHandle(); + } catch (IllegalStateException ex){ + // This exception means that getCurrentCase() failed because no case was open. + // This can happen when the user closes the case while ingest is ongoing - canceling + // ingest fires off the DataSourceAnalysisCompletedEvent while the case is in the + // process of closing. + logger.log(Level.WARNING, String.format("Case closed before ImageWriter could start the finishing process for %s", + dataSourceName)); + return; } catch (TskCoreException ex){ logger.log(Level.SEVERE, "Error loading image", ex); - imageHandle = null; return; }