diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 285e9b9174..01b30b1fd1 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -73,6 +73,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; @@ -327,7 +328,7 @@ public class Case implements SleuthkitCase.ErrorObserver { SwingUtilities.invokeLater(() -> { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); - IngestManager.getInstance().cancelAllIngestJobs(); + IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED); doCaseChange(null); //closes windows, etc if (null != oldCase.tskErrorReporter) { oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index f14a5d61b3..9c5764a383 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -118,6 +118,8 @@ final class DataSourceIngestJob { */ private volatile boolean currentDataSourceIngestModuleCancelled; private volatile boolean cancelled; + private volatile IngestJob.CancellationReason cancellationReason = IngestJob.CancellationReason.NOT_CANCELLED; + private final Object cancellationStateMonitor = new Object(); private final List cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>(); /** @@ -160,12 +162,12 @@ final class DataSourceIngestJob { * Constructs an object that encapsulates a data source and the ingest * module pipelines used to process it. * - * @param parentJob The ingest job of which this data source ingest job is a - * part. - * @param dataSource The data source to be ingested. - * @param settings The settings for the ingest job. + * @param parentJob The ingest job of which this data source ingest + * job is a part. + * @param dataSource The data source to be ingested. + * @param settings The settings for the ingest job. * @param runInteractively Whether or not this job should use NetBeans - * progress handles. + * progress handles. */ DataSourceIngestJob(IngestJob parentJob, Content dataSource, IngestJobSettings settings, boolean runInteractively) { this.parentJob = parentJob; @@ -252,12 +254,12 @@ final class DataSourceIngestJob { * collection as they are added to the output collection. * * @param ingestModuleTemplates A mapping of ingest module factory class - * names to ingest module templates. - * @param pipelineConfig An ordered list of ingest module factory class - * names representing an ingest pipeline. + * names to ingest module templates. + * @param pipelineConfig An ordered list of ingest module factory + * class names representing an ingest pipeline. * * @return An ordered list of ingest module templates, i.e., an - * uninstantiated pipeline. + * uninstantiated pipeline. */ private static List getConfiguredIngestModuleTemplates(Map ingestModuleTemplates, List pipelineConfig) { List templates = new ArrayList<>(); @@ -498,7 +500,7 @@ final class DataSourceIngestJob { String dialogTitle = NbBundle.getMessage(DataSourceIngestJob.this.getClass(), "IngestJob.cancellationDialog.title"); JOptionPane.showConfirmDialog(null, panel, dialogTitle, JOptionPane.OK_OPTION, JOptionPane.PLAIN_MESSAGE); if (panel.cancelAllDataSourceIngestModules()) { - DataSourceIngestJob.this.cancel(); + DataSourceIngestJob.this.cancel(IngestJob.CancellationReason.USER_CANCELLED); } else { DataSourceIngestJob.this.cancelCurrentDataSourceIngestModule(); } @@ -527,7 +529,7 @@ final class DataSourceIngestJob { // the cancel button on the progress bar and the OK button // of a cancelation confirmation dialog supplied by // NetBeans. - DataSourceIngestJob.this.cancel(); + DataSourceIngestJob.this.cancel(IngestJob.CancellationReason.USER_CANCELLED); return true; } }); @@ -672,8 +674,9 @@ final class DataSourceIngestJob { * @param task A file ingest task. * * @throws InterruptedException if the thread executing this code is - * interrupted while blocked on taking from or putting to the file ingest - * pipelines collection. + * interrupted while blocked on taking from or + * putting to the file ingest pipelines + * collection. */ void process(FileIngestTask task) throws InterruptedException { try { @@ -774,7 +777,7 @@ final class DataSourceIngestJob { * process the data source is known. * * @param workUnits Total number of work units for the processing of the - * data source. + * data source. */ void switchDataSourceIngestProgressBarToDeterminate(int workUnits) { if (this.doUI && !this.cancelled) { @@ -839,7 +842,7 @@ final class DataSourceIngestJob { * mode. The task name is the "subtitle" under the display name. * * @param currentTask The task name. - * @param workUnits Number of work units performed. + * @param workUnits Number of work units performed. */ void advanceDataSourceIngestProgressBar(String currentTask, int workUnits) { if (this.doUI && !this.cancelled) { @@ -910,8 +913,10 @@ final class DataSourceIngestJob { /** * Requests cancellation of ingest, i.e., a shutdown of the data source * level and file level ingest pipelines. + * + * @param reason The cancellation reason. */ - void cancel() { + void cancel(IngestJob.CancellationReason reason) { if (this.doUI) { /** * Put a cancellation message on data source level ingest progress @@ -951,7 +956,14 @@ final class DataSourceIngestJob { } } - this.cancelled = true; + synchronized (cancellationStateMonitor) { + /* + * These fields are volatile for reading, synchronized on the + * monitor here for writing. + */ + this.cancelled = true; + this.cancellationReason = reason; + } /** * Tell the task scheduler to cancel all pending tasks, i.e., tasks not @@ -964,9 +976,9 @@ final class DataSourceIngestJob { /** * Set the current module name being run and the file name it is running on. * To be used for more detailed cancelling. - * + * * @param moduleName Name of module currently running. - * @param taskName Name of file the module is running on. + * @param taskName Name of file the module is running on. */ void setCurrentFileIngestModule(String moduleName, String taskName) { this.currentFileIngestModule = moduleName; @@ -983,6 +995,15 @@ final class DataSourceIngestJob { return this.cancelled; } + /** + * Gets the reason this job was cancelled. + * + * @return The cancellation reason, may be not cancelled. + */ + IngestJob.CancellationReason getCancellationReason() { + return this.cancellationReason; + } + /** * Write ingest module errors to the log. * @@ -1019,6 +1040,7 @@ final class DataSourceIngestJob { private final long estimatedFilesToProcess; private final IngestTasksScheduler.IngestJobTasksSnapshot tasksSnapshot; private final boolean jobCancelled; + private final IngestJob.CancellationReason jobCancellationReason; private final List cancelledDataSourceModules; /** @@ -1047,6 +1069,7 @@ final class DataSourceIngestJob { } this.jobCancelled = cancelled; + this.jobCancellationReason = cancellationReason; this.cancelledDataSourceModules = new ArrayList<>(DataSourceIngestJob.this.cancelledDataSourceIngestModules); if (getIngestTasksSnapshot) { @@ -1069,7 +1092,7 @@ final class DataSourceIngestJob { * Gets time these statistics were collected. * * @return The statistics collection time as number of milliseconds - * since January 1, 1970, 00:00:00 GMT. + * since January 1, 1970, 00:00:00 GMT. */ long getSnapshotTime() { return snapShotTime; @@ -1099,7 +1122,7 @@ final class DataSourceIngestJob { * Gets the time the ingest job was started. * * @return The start time as number of milliseconds since January 1, - * 1970, 00:00:00 GMT. + * 1970, 00:00:00 GMT. */ long getJobStartTime() { return jobStartTime; @@ -1185,12 +1208,21 @@ final class DataSourceIngestJob { return this.jobCancelled; } + /** + * Gets the reason this job was cancelled. + * + * @return The cancellation reason, may be not cancelled. + */ + IngestJob.CancellationReason getCancellationReason() { + return this.jobCancellationReason; + } + /** * Gets a list of the display names of any canceled data source level * ingest modules * * @return A list of canceled data source level ingest module display - * names, possibly empty. + * names, possibly empty. */ List getCancelledDataSourceIngestModules() { return Collections.unmodifiableList(this.cancelledDataSourceModules); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java index 941e8de8ca..e43de89b1c 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java @@ -37,12 +37,26 @@ import org.sleuthkit.datamodel.Content; */ public final class IngestJob { + /* + * An ingest job can be cancelled for various reasons. + */ + public enum CancellationReason { + + NOT_CANCELLED, + USER_CANCELLED, + INGEST_MODULES_STARTUP_FAILED, + OUT_OF_DISK_SPACE, + SERVICES_DOWN, + CASE_CLOSED + } + private static final AtomicLong nextId = new AtomicLong(0L); private final long id; private final Map dataSourceJobs; private final AtomicInteger incompleteJobsCount; - private boolean started; // Guarded by this - private volatile boolean cancelled; + private boolean started; + private boolean cancelled; + private CancellationReason cancellationReason; /** * Constructs an ingest job that runs a collection of data sources through a @@ -61,6 +75,7 @@ public final class IngestJob { this.dataSourceJobs.put(dataSourceIngestJob.getId(), dataSourceIngestJob); } incompleteJobsCount = new AtomicInteger(dataSourceJobs.size()); + cancellationReason = CancellationReason.NOT_CANCELLED; } /** @@ -108,19 +123,32 @@ public final class IngestJob { } started = true; - List startedDataSourceJobs = new ArrayList<>(); + /* + * Try to start each data source ingest job. Note that there is a not + * unwarranted assumption here that if there is going to be a module + * startup failure, it will be for the first data source ingest job. + * + * TODO (RC): Consider separating module start up from pipeline startup + * so that no processing is done if this assumption is false. + */ for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) { errors.addAll(dataSourceJob.start()); - if (errors.isEmpty()) { - IngestManager.getInstance().fireDataSourceAnalysisStarted(id, dataSourceJob.getId(), dataSourceJob.getDataSource()); - startedDataSourceJobs.add(dataSourceJob); - } else { - startedDataSourceJobs.stream().forEach((startedDataSourceJob) -> { - startedDataSourceJob.cancel(); - }); + if (errors.isEmpty() == false) { break; } } + + /* + * Handle start up success or failure. + */ + if (errors.isEmpty()) { + for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) { + IngestManager.getInstance().fireDataSourceAnalysisStarted(id, dataSourceJob.getId(), dataSourceJob.getDataSource()); + } + } else { + cancel(CancellationReason.INGEST_MODULES_STARTUP_FAILED); + } + return errors; } @@ -161,13 +189,37 @@ public final class IngestJob { * unfinished tasks and stopping the ingest pipelines. Returns immediately, * but there may be a delay before all of the ingest modules in the * pipelines respond by stopping processing. + * + * @deprecated Use cancel(CancellationReason reason) instead */ + @Deprecated synchronized public void cancel() { - IngestManager ingestManager = IngestManager.getInstance(); + cancel(CancellationReason.USER_CANCELLED); + } + + /** + * Requests cancellation of this ingest job, which means discarding + * unfinished tasks and stopping the ingest pipelines. Returns immediately, + * but there may be a delay before all of the ingest modules in the + * pipelines respond by stopping processing. + * + * @param reason The reason for cancellation. + */ + synchronized public void cancel(CancellationReason reason) { this.dataSourceJobs.values().stream().forEach((job) -> { - job.cancel(); + job.cancel(reason); }); this.cancelled = true; + this.cancellationReason = reason; + } + + /** + * Gets the reason this job was cancelled. + * + * @return The cancellation reason, may be not cancelled. + */ + synchronized public CancellationReason getCancellationReason() { + return this.cancellationReason; } /** @@ -176,7 +228,7 @@ public final class IngestJob { * * @return True or false. */ - public boolean isCancelled() { + synchronized public boolean isCancelled() { return this.cancelled; } @@ -208,6 +260,7 @@ public final class IngestJob { private boolean fileIngestRunning; private Date fileIngestStartTime; private final boolean jobCancelled; + private final IngestJob.CancellationReason jobCancellationReason; /** * A snapshot of the progress of an ingest job on the processing of a @@ -241,6 +294,15 @@ public final class IngestJob { return snapshot.isCancelled(); } + /** + * Gets the reason this job was cancelled. + * + * @return The cancellation reason, may be not cancelled. + */ + synchronized public CancellationReason getCancellationReason() { + return snapshot.getCancellationReason(); + } + /** * Gets a list of the display names of any canceled data source * level ingest modules. @@ -280,6 +342,7 @@ public final class IngestJob { } } this.jobCancelled = cancelled; + this.jobCancellationReason = cancellationReason; } /** @@ -312,8 +375,8 @@ public final class IngestJob { } /** - * Queries whether or not a cancellation request had been issued at the - * time the snapshot was taken. + * Queries whether or not an ingest job level cancellation request had + * been issued at the time the snapshot was taken. * * @return True or false. */ @@ -321,6 +384,15 @@ public final class IngestJob { return this.jobCancelled; } + /** + * Gets the reason this job was cancelled. + * + * @return The cancellation reason, may be not cancelled. + */ + synchronized public CancellationReason getCancellationReason() { + return this.jobCancellationReason; + } + /** * Gets snapshots of the progress processing individual data sources. * diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index e8f2a797d5..263bbe275b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -357,7 +357,7 @@ public class IngestManager { } // cancel ingest if running - cancelAllIngestJobs(); + cancelAllIngestJobs(IngestJob.CancellationReason.SERVICES_DOWN); } } }; @@ -627,8 +627,21 @@ public class IngestManager { /** * Cancels all ingest jobs in progress. + * + * @deprecated Use cancelAllIngestJobs(IngestJob.CancellationReason reason) + * instead. */ + @Deprecated public synchronized void cancelAllIngestJobs() { + cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED); + } + + /** + * Cancels all ingest jobs in progress. + * + * @param reason The cancellation reason. + */ + public synchronized void cancelAllIngestJobs(IngestJob.CancellationReason reason) { // Stop creating new ingest jobs. for (Future handle : ingestJobStarters.values()) { handle.cancel(true); @@ -636,7 +649,7 @@ public class IngestManager { // Cancel all the jobs already created. for (IngestJob job : this.jobsById.values()) { - job.cancel(); + job.cancel(reason); } } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java index 91da8d183a..1a895d9b92 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java @@ -196,7 +196,7 @@ public final class IngestMonitor { /* * Shut down ingest by cancelling all ingest jobs. */ - manager.cancelAllIngestJobs(); + manager.cancelAllIngestJobs(IngestJob.CancellationReason.OUT_OF_DISK_SPACE); String diskPath = root.getAbsolutePath(); IngestServices.getInstance().postMessage(IngestMessage.createManagerErrorMessage( NbBundle.getMessage(this.getClass(), "IngestMonitor.mgrErrMsg.lowDiskSpace.title", diskPath),