Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
IngestJobExecutor.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2014-2021 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.ingest;
20
21import java.lang.reflect.InvocationTargetException;
22import java.util.ArrayList;
23import java.util.Date;
24import java.util.HashSet;
25import java.util.List;
26import java.util.Optional;
27import java.util.Set;
28import java.util.concurrent.CopyOnWriteArrayList;
29import java.util.logging.Level;
30import javax.annotation.concurrent.GuardedBy;
31import javax.swing.JOptionPane;
32import javax.swing.SwingUtilities;
33import org.netbeans.api.progress.ProgressHandle;
34import org.openide.util.Cancellable;
35import org.openide.util.NbBundle;
36import org.openide.util.NbBundle.Messages;
37import org.openide.windows.WindowManager;
38import org.sleuthkit.autopsy.casemodule.Case;
39import org.sleuthkit.autopsy.core.RuntimeProperties;
40import org.sleuthkit.autopsy.coreutils.Logger;
41import org.sleuthkit.autopsy.coreutils.NetworkUtils;
42import org.sleuthkit.autopsy.coreutils.ThreadConfined;
43import org.sleuthkit.datamodel.AbstractFile;
44import org.sleuthkit.datamodel.IngestJobInfo;
45import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType;
46import org.sleuthkit.datamodel.IngestModuleInfo;
47import org.sleuthkit.datamodel.IngestModuleInfo.IngestModuleType;
48import org.sleuthkit.datamodel.SleuthkitCase;
49import org.sleuthkit.datamodel.TskCoreException;
50import org.sleuthkit.autopsy.modules.interestingitems.FilesSet;
51import org.sleuthkit.autopsy.python.FactoryClassNameNormalizer;
52import org.sleuthkit.datamodel.AnalysisResult;
53import org.sleuthkit.datamodel.DataArtifact;
54import org.sleuthkit.datamodel.DataSource;
55
61final class IngestJobExecutor {
62
69 private static final Logger logger = Logger.getLogger(IngestJobExecutor.class.getName());
70 private final IngestJob ingestJob;
71 private final long createTime;
72 private final boolean usingNetBeansGUI;
73 private final IngestTasksScheduler taskScheduler = IngestTasksScheduler.getInstance();
74 private final Object threadRegistrationLock = new Object();
75 @GuardedBy("threadRegistrationLock")
76 private final Set<Thread> pausedIngestThreads = new HashSet<>();
77 private final List<String> cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>();
78 private final Object tierTransitionLock = new Object();
79 private final List<IngestModuleTier> ingestModuleTiers = new ArrayList<>();
80 private volatile int moduleTierIndex = 0;
81 private volatile IngestJobState jobState = IngestJobExecutor.IngestJobState.PIPELINES_STARTING_UP;
82 private volatile long estimatedFilesToProcess = 0;
83 private volatile long processedFiles = 0;
84 private volatile boolean currentDataSourceIngestModuleCancelled = false;
85 private volatile boolean jobCancelled = false;
86 private volatile IngestJob.CancellationReason cancellationReason = IngestJob.CancellationReason.NOT_CANCELLED;
87 private volatile IngestJobInfo casDbingestJobInfo;
88 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
89 private ProgressHandle dataSourceIngestProgressBar;
90 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
91 private final List<String> filesInProgress = new ArrayList<>();
92 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
93 private ProgressHandle fileIngestProgressBar;
94 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
95 private ProgressHandle artifactIngestProgressBar;
96 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
97 private ProgressHandle resultIngestProgressBar;
98
111 IngestJobExecutor(IngestJob ingestJob) throws InterruptedException {
112 this.ingestJob = ingestJob;
113 createTime = new Date().getTime();
114 /*
115 * If running in the NetBeans thick client application version of
116 * Autopsy, NetBeans progress handles (i.e., progress bars) are used to
117 * display ingest job progress in the lower right hand corner of the
118 * main application window. A layer of abstraction to allow alternate
119 * representations of progress could be used here, as it is in some
120 * other places in the application (see implementations and usage of the
121 * org.sleuthkit.autopsy.progress.ProgressIndicator interface).
122 */
123 usingNetBeansGUI = RuntimeProperties.runningWithGUI();
124 }
125
131 long getIngestJobId() {
132 return ingestJob.getId();
133 }
134
141 String getExecutionContext() {
142 return ingestJob.getSettings().getExecutionContext();
143 }
144
151 DataSource getDataSource() {
152 return ingestJob.getDataSource();
153 }
154
161 boolean shouldProcessUnallocatedSpace() {
162 return ingestJob.getSettings().getProcessUnallocatedSpace();
163 }
164
171 FilesSet getFileIngestFilter() {
172 return ingestJob.getSettings().getFileFilter();
173 }
174
190 List<IngestModuleError> startUp() throws InterruptedException {
192 ingestModuleTiers.addAll(IngestModuleTierBuilder.buildIngestModuleTiers(ingestJob.getSettings(), this));
193 List<IngestModuleError> errors = startUpIngestModulePipelines();
194 if (errors.isEmpty()) {
195 recordIngestJobStartUpInfo();
196 /*
197 * Start up and execution of the first ingest module tier requires
198 * some special treatment due to the differences between streaming
199 * and batch mode ingest jobs. Subsequent tiers can be handled
200 * generically.
201 */
202 if (ingestJob.getIngestMode() == IngestJob.Mode.STREAMING) {
203 startStreamingModeAnalysis();
204 } else {
205 startBatchModeAnalysis();
206 }
207 }
208 return errors;
209 }
210
216 private List<IngestModuleError> startUpIngestModulePipelines() {
217 List<IngestModuleError> errors = new ArrayList<>();
218 for (IngestModuleTier moduleTier : ingestModuleTiers) {
219 Optional<DataSourceIngestPipeline> dataSourcePipeline = moduleTier.getDataSourceIngestPipeline();
220 if (dataSourcePipeline.isPresent()) {
221 errors.addAll(startUpIngestModulePipeline(dataSourcePipeline.get()));
222 }
223
224 for (FileIngestPipeline pipeline : moduleTier.getFileIngestPipelines()) {
225 List<IngestModuleError> filePipelineErrors = startUpIngestModulePipeline(pipeline);
226 if (!filePipelineErrors.isEmpty()) {
227 /*
228 * If one file pipeline copy can't start up, assume that
229 * none of the other copies will be able to start up, for
230 * the same reason.
231 */
232 errors.addAll(filePipelineErrors);
233 break;
234 }
235 }
236
237 Optional<DataArtifactIngestPipeline> dataArtifactPipeline = moduleTier.getDataArtifactIngestPipeline();
238 if (dataArtifactPipeline.isPresent()) {
239 errors.addAll(startUpIngestModulePipeline(dataArtifactPipeline.get()));
240 }
241
242 Optional<AnalysisResultIngestPipeline> analysisResultPipeline = moduleTier.getAnalysisResultIngestPipeline();
243 if (analysisResultPipeline.isPresent()) {
244 errors.addAll(startUpIngestModulePipeline(analysisResultPipeline.get()));
245 }
246 }
247 return errors;
248 }
249
258 private List<IngestModuleError> startUpIngestModulePipeline(IngestPipeline<?> pipeline) {
259 List<IngestModuleError> startUpErrors = pipeline.startUp();
260 if (!startUpErrors.isEmpty()) {
261 List<IngestModuleError> shutDownErrors = pipeline.shutDown();
262 if (!shutDownErrors.isEmpty()) {
263 logIngestModuleErrors(shutDownErrors);
264 }
265 }
266 return startUpErrors;
267 }
268
274 private void recordIngestJobStartUpInfo() {
275 try {
276 SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase();
277 List<IngestModuleInfo> ingestModuleInfoList = new ArrayList<>();
278 for (IngestModuleTemplate module : ingestJob.getSettings().getEnabledIngestModuleTemplates()) {
279 IngestModuleType moduleType = getIngestModuleTemplateType(module);
280 IngestModuleInfo moduleInfo = caseDb.addIngestModule(module.getModuleName(), FactoryClassNameNormalizer.normalize(module.getModuleFactory().getClass().getCanonicalName()), moduleType, module.getModuleFactory().getModuleVersionNumber());
281 ingestModuleInfoList.add(moduleInfo);
282 }
283 casDbingestJobInfo = caseDb.addIngestJob(ingestJob.getDataSource(), NetworkUtils.getLocalHostName(), ingestModuleInfoList, new Date(this.createTime), new Date(0), IngestJobStatusType.STARTED, "");
284 } catch (TskCoreException ex) {
285 logErrorMessage(Level.SEVERE, "Failed to add ingest job info to case database", ex); //NON-NLS
286 }
287 }
288
297 private static IngestModuleType getIngestModuleTemplateType(IngestModuleTemplate moduleTemplate) {
298 IngestModuleType type = null;
299 if (moduleTemplate.isDataSourceIngestModuleTemplate()) {
300 type = IngestModuleType.DATA_SOURCE_LEVEL;
301 }
302 if (moduleTemplate.isFileIngestModuleTemplate()) {
303 if (type == null) {
304 type = IngestModuleType.FILE_LEVEL;
305 } else {
306 type = IngestModuleType.MULTIPLE;
307 }
308 }
309 if (moduleTemplate.isDataArtifactIngestModuleTemplate()) {
310 if (type == null) {
311 type = IngestModuleType.DATA_ARTIFACT;
312 } else {
313 type = IngestModuleType.MULTIPLE;
314 }
315 }
316 return type;
317 }
318
325 private void startBatchModeAnalysis() {
326 synchronized (tierTransitionLock) {
327 logInfoMessage("Starting ingest job in file batch mode"); //NON-NLS
328 jobState = IngestJobState.ANALYZING;
329 IngestModuleTier currentTier = ingestModuleTiers.get(moduleTierIndex);
330
331 if (currentTier.hasDataSourceIngestModules()) {
332 startDataSourceIngestProgressBar();
333 taskScheduler.scheduleDataSourceIngestTask(this);
334 }
335
336 if (currentTier.hasFileIngestModules()) {
337 estimateFilesToProcess();
338 startFileIngestProgressBar(true);
339 taskScheduler.scheduleFileIngestTasks(this, ingestJob.getFiles());
340 }
341
342 if (currentTier.hasDataArtifactIngestModules()) {
343 /*
344 * Analysis of any data artifacts already in the case database
345 * (possibly added by the DSP) will be performed.
346 */
347 startDataArtifactIngestProgressBar();
348 taskScheduler.scheduleDataArtifactIngestTasks(this);
349 }
350
351 if (currentTier.hasAnalysisResultIngestModules()) {
352 /*
353 * Analysis of any analysis results already in the case database
354 * (possibly added by the DSP) will be performed.
355 */
356 startAnalysisResultIngestProgressBar();
357 taskScheduler.scheduleAnalysisResultIngestTasks(this);
358 }
359
360 /*
361 * Check for analysis completion. This is necessary because it is
362 * possible that none of the tasks that were just scheduled will
363 * actually make it to task execution, due to the file filter or
364 * other ingest job settings. If that happens, there will never be
365 * another analysis completion check for this job in an ingest
366 * thread executing an ingest task, so such a job would run forever,
367 * doing nothing, without a check here.
368 */
369 checkForTierCompleted(moduleTierIndex);
370 }
371 }
372
376 private void estimateFilesToProcess() {
377 estimatedFilesToProcess = 0;
378 processedFiles = 0;
379 if (ingestModuleTiers.get(moduleTierIndex).hasFileIngestModules()) {
380 /*
381 * Do an estimate of the total number of files to be analyzed. This
382 * will be used to estimate of how many files remain to be analyzed
383 * as each file ingest task is completed. The numbers are estimates
384 * because analysis can add carved and/or derived files to the job.
385 */
386 List<AbstractFile> files = ingestJob.getFiles();
387 if (files.isEmpty()) {
388 /*
389 * Do a count of the files from the data source that the data
390 * source processor (DSP) has added to the case database.
391 */
392 estimatedFilesToProcess = ingestJob.getDataSource().accept(new GetFilesCountVisitor());
393 } else {
394 /*
395 * Otherwise, this job is analyzing a user-specified subset of
396 * the files in the data source.
397 */
398 estimatedFilesToProcess = files.size();
399 }
400 }
401 }
402
410 private void startStreamingModeAnalysis() {
411 synchronized (tierTransitionLock) {
412 logInfoMessage("Starting ingest job in file streaming mode"); //NON-NLS
414 IngestModuleTier currentTier = ingestModuleTiers.get(moduleTierIndex);
415
416 if (currentTier.hasFileIngestModules()) {
417 /*
418 * Start the file ingest progress bar, but do not schedule any
419 * file or data source ingest tasks. File ingest tasks will
420 * instead be scheduled as files are streamed in via
421 * addStreamedFiles(), and a data source ingest task will be
422 * scheduled later, via addStreamedDataSource().
423 */
424 startFileIngestProgressBar(false);
425 }
426
427 if (currentTier.hasDataArtifactIngestModules()) {
428 /*
429 * Start the data artifact progress bar and schedule ingest
430 * tasks for any data artifacts currently in the case database.
431 * This needs to be done BEFORE any files or the data source are
432 * streamed in to ensure that any data artifacts added to the
433 * case database by the file and data source ingest tasks are
434 * not analyzed twice. This works here because the ingest
435 * manager has not yet returned the ingest stream object that is
436 * used to call addStreamedFiles() and addStreamedDataSource().
437 */
438 startDataArtifactIngestProgressBar();
439 taskScheduler.scheduleDataArtifactIngestTasks(this);
440 }
441
442 if (currentTier.hasAnalysisResultIngestModules()) {
443 /*
444 * Start the analysis result progress bar and schedule ingest
445 * tasks for any analysis results currently in the case
446 * database. This needs to be done BEFORE any files or the data
447 * source are streamed in to ensure that any analysis results
448 * added to the case database by the file and data source ingest
449 * tasks are not analyzed twice. This works here because the
450 * ingest manager has not yet returned the ingest stream object
451 * that is used to call addStreamedFiles() and
452 * addStreamedDataSource().
453 */
454 startAnalysisResultIngestProgressBar();
455 taskScheduler.scheduleAnalysisResultIngestTasks(this);
456 }
457 }
458 }
459
465 void addStreamedDataSource() {
466 synchronized (tierTransitionLock) {
467 logInfoMessage("Data source received in streaming mode ingest job"); //NON-NLS
468 jobState = IngestJobExecutor.IngestJobState.ANALYZING;
469 IngestModuleTier currentTier = ingestModuleTiers.get(moduleTierIndex);
470
471 if (currentTier.hasFileIngestModules()) {
472 estimateFilesToProcess();
473 switchFileIngestProgressBarToDeterminate();
474 // We don't need to schedule file tasks here because they've already been
475 // added as the data source was being processed
476 }
477
478 if (currentTier.hasDataSourceIngestModules()) {
479 taskScheduler.scheduleDataSourceIngestTask(this);
480 startDataSourceIngestProgressBar();
481 } else {
482 /*
483 * If no data source level ingest task is scheduled at this
484 * time, and all of the file level and artifact ingest tasks
485 * scheduled during the initial file streaming stage have
486 * already been executed, there will never be a stage completion
487 * check in an ingest thread executing an ingest task for this
488 * job, so such a job would run forever, doing nothing, without
489 * a check here.
490 */
491 checkForTierCompleted(moduleTierIndex);
492 }
493 }
494 }
495
501 private void checkForTierCompleted(int currentTier) {
502 synchronized (tierTransitionLock) {
504 return;
505 }
506 if (currentTier < moduleTierIndex) {
507 // We likely had a leftover task from the previous tier. Since we've already
508 // advanced to the next tier, ignore it.
509 return;
510 }
511 if (taskScheduler.currentTasksAreCompleted(getIngestJobId())) {
512 do {
513 shutDownCurrentTier();
514 moduleTierIndex++;
515 if (moduleTierIndex < ingestModuleTiers.size()) {
516 startAnalysisForCurrentTier();
517 } else {
518 shutDown();
519 break;
520 }
521 } while (taskScheduler.currentTasksAreCompleted(getIngestJobId())); // Loop again immediately in case the new tier is empty
522 }
523 }
524 }
525
530 private void startAnalysisForCurrentTier() {
531 logInfoMessage(String.format("Scheduling ingest tasks for tier %s of ingest job", moduleTierIndex)); //NON-NLS
532 jobState = IngestJobExecutor.IngestJobState.ANALYZING;
533 IngestModuleTier currentTier = ingestModuleTiers.get(moduleTierIndex);
534
535 if (currentTier.hasDataSourceIngestModules()) {
536 startDataSourceIngestProgressBar();
537 taskScheduler.scheduleDataSourceIngestTask(this);
538 }
539
540 if (currentTier.hasFileIngestModules()) {
541 estimateFilesToProcess();
542 startFileIngestProgressBar(true);
543 taskScheduler.scheduleFileIngestTasks(this, ingestJob.getFiles());
544 }
545
546 if (currentTier.hasDataArtifactIngestModules()) {
547 startDataArtifactIngestProgressBar();
548 }
549
550 if (currentTier.hasAnalysisResultIngestModules()) {
551 startDataArtifactIngestProgressBar();
552 }
553 }
554
562 void execute(DataSourceIngestTask task) {
563 try {
564 if (!isCancelled()) {
565 Optional<DataSourceIngestPipeline> pipeline = ingestModuleTiers.get(moduleTierIndex).getDataSourceIngestPipeline();
566 if (pipeline.isPresent()) {
567 List<IngestModuleError> errors = new ArrayList<>();
568 errors.addAll(pipeline.get().performTask(task));
569 if (!errors.isEmpty()) {
570 logIngestModuleErrors(errors);
571 }
572 }
573 }
574 } finally {
575 // Save the module tier assocaited with this task since it could change after
576 // notifyTaskComplete
577 int currentTier = moduleTierIndex;
578 taskScheduler.notifyTaskCompleted(task);
579 checkForTierCompleted(currentTier);
580 }
581 }
582
590 void execute(FileIngestTask task) {
591 try {
592 if (!isCancelled()) {
593 FileIngestPipeline pipeline = ingestModuleTiers.get(moduleTierIndex).takeFileIngestPipeline();
594 if (!pipeline.isEmpty()) {
595 /*
596 * Get the file from the task. If the file was streamed in,
597 * the task may only have the file object ID, and a trip to
598 * the case database will be required.
599 */
600 AbstractFile file;
601 try {
602 file = task.getFile();
603 } catch (TskCoreException ex) {
604 List<IngestModuleError> errors = new ArrayList<>();
605 errors.add(new IngestModuleError("Ingest Pipeline", ex));
606 logIngestModuleErrors(errors);
607 ingestModuleTiers.get(moduleTierIndex).returnFileIngestPipeleine(pipeline);
608 return;
609 }
610
615 final String fileName = file.getName();
616 processedFiles++;
617 updateFileProgressBarForFileTaskStarted(fileName);
618 List<IngestModuleError> errors = new ArrayList<>();
619 errors.addAll(pipeline.performTask(task));
620 if (!errors.isEmpty()) {
621 logIngestModuleErrors(errors, file);
622 }
623 updateFileProgressBarForFileTaskCompleted(fileName);
624 }
625 ingestModuleTiers.get(moduleTierIndex).returnFileIngestPipeleine(pipeline);
626 }
627 } catch (InterruptedException ex) {
628 logger.log(Level.SEVERE, String.format("File ingest thread interrupted during execution of file ingest job (file object ID = %d, thread ID = %d)", task.getFileId(), task.getThreadId()), ex);
629 Thread.currentThread().interrupt();
630 } finally {
631 // Save the module tier assocaited with this task since it could change after
632 // notifyTaskComplete
633 int currentTier = moduleTierIndex;
634 taskScheduler.notifyTaskCompleted(task);
635 checkForTierCompleted(currentTier);
636 }
637 }
638
646 void execute(DataArtifactIngestTask task) {
647 try {
648 if (!isCancelled()) {
649 Optional<DataArtifactIngestPipeline> pipeline = ingestModuleTiers.get(moduleTierIndex).getDataArtifactIngestPipeline();
650 if (pipeline.isPresent()) {
651 List<IngestModuleError> errors = new ArrayList<>();
652 errors.addAll(pipeline.get().performTask(task));
653 if (!errors.isEmpty()) {
654 logIngestModuleErrors(errors);
655 }
656 }
657 }
658 } finally {
659 // Save the module tier assocaited with this task since it could change after
660 // notifyTaskComplete
661 int currentTier = moduleTierIndex;
662 taskScheduler.notifyTaskCompleted(task);
663 checkForTierCompleted(currentTier);
664 }
665 }
666
674 void execute(AnalysisResultIngestTask task) {
675 try {
676 if (!isCancelled()) {
677 Optional<AnalysisResultIngestPipeline> pipeline = ingestModuleTiers.get(moduleTierIndex).getAnalysisResultIngestPipeline();
678 if (pipeline.isPresent()) {
679 List<IngestModuleError> errors = new ArrayList<>();
680 errors.addAll(pipeline.get().performTask(task));
681 if (!errors.isEmpty()) {
682 logIngestModuleErrors(errors);
683 }
684 }
685 }
686 } finally {
687 // Save the module tier assocaited with this task since it could change after
688 // notifyTaskComplete
689 int currentTier = moduleTierIndex;
690 taskScheduler.notifyTaskCompleted(task);
691 checkForTierCompleted(currentTier);
692 }
693 }
694
700 void addStreamedFiles(List<Long> fileObjIds) {
701 if (!isCancelled() && ingestModuleTiers.get(moduleTierIndex).hasFileIngestModules()) {
703 taskScheduler.scheduleStreamedFileIngestTasks(this, fileObjIds);
704 } else {
705 logErrorMessage(Level.SEVERE, "Adding streaming files to job during stage " + jobState.toString() + " not supported");
706 }
707 }
708 }
709
721 void addFiles(List<AbstractFile> files) {
722 if (!isCancelled() && ingestModuleTiers.get(moduleTierIndex).hasFileIngestModules()) {
723 if (jobState.equals(IngestJobState.ACCEPTING_STREAMED_CONTENT_AND_ANALYZING) || jobState.equals(IngestJobState.ANALYZING)) {
724 taskScheduler.scheduleHighPriorityFileIngestTasks(this, files);
725 } else {
726 logErrorMessage(Level.SEVERE, "Adding files to job during stage " + jobState.toString() + " not supported");
727 }
728 }
729 }
730
741 void addDataArtifacts(List<DataArtifact> artifacts) {
742 if (!isCancelled() && ingestModuleTiers.get(moduleTierIndex).hasDataArtifactIngestModules()) {
743 switch (jobState) {
744 case ACCEPTING_STREAMED_CONTENT_AND_ANALYZING:
745 case ANALYZING:
746 taskScheduler.scheduleDataArtifactIngestTasks(this, artifacts);
747 break;
748 case PIPELINES_SHUTTING_DOWN:
749 /*
750 * Don't log an error if there is an attempt to add an
751 * data artifact ingest task in a pipeline shut down
752 * state. This is a work around for dealing with data
753 * artifacts generated by a final keyword search carried out
754 * during ingest module shut down by simply ignoring them.
755 * (Currently these are credit card accounts generated by
756 * keyword search). Other ideas were to add
757 * a startShutDown() phase to the ingest module
758 * life cycle (complicated), or to add a flag
759 * to keyword hit processing to suppress posting the keyword
760 * hit analysis results / data artifacts to the blackboard during a final
761 * search (API changes required to allow firing of the event
762 * to make any GUI refresh).
763 */
764 break;
765 default:
766 logErrorMessage(Level.SEVERE, "Attempt to add data artifacts to job during stage " + jobState.toString() + " not supported");
767 break;
768 }
769 }
770 }
771
782 void addAnalysisResults(List<AnalysisResult> results) {
783 if (!isCancelled() && ingestModuleTiers.get(moduleTierIndex).hasAnalysisResultIngestModules()) {
784 switch (jobState) {
785 case ACCEPTING_STREAMED_CONTENT_AND_ANALYZING:
786 case ANALYZING:
787 taskScheduler.scheduleAnalysisResultIngestTasks(this, results);
788 break;
789 case PIPELINES_SHUTTING_DOWN:
790 /*
791 * Don't log an error if there is an attempt to add an
792 * analysis result ingest task in a pipeline shut down
793 * state. This is a work around for dealing with analysis
794 * results generated by a final keyword search carried out
795 * during ingest module shut down by simply ignoring them.
796 * Other ideas were to add a startShutDown() phase to the
797 * ingest module life cycle (complicated), or to add a flag
798 * to keyword hit processing to suppress posting the keyword
799 * hit analysis results to the blackboard during a final
800 * search (API changes required to allow firing of the event
801 * to make any GUI refresh).
802 */
803 break;
804 default:
805 logErrorMessage(Level.SEVERE, "Attempt to add analysis results to job during stage " + jobState.toString() + " not supported");
806 }
807 }
808 }
809
813 private void shutDownCurrentTier() {
814 // Note that this method is only called while holding the tierTransitionLock, so moduleTierIndex can not change
815 // during execution.
816 if (moduleTierIndex >= ingestModuleTiers.size()) {
817 logErrorMessage(Level.SEVERE, "shutDownCurrentTier called with out-of-bounds moduleTierIndex (" + moduleTierIndex + ")");
818 return;
819 }
820 logInfoMessage(String.format("Finished all ingest tasks for tier %s of ingest job", moduleTierIndex)); //NON-NLS
821 jobState = IngestJobExecutor.IngestJobState.PIPELINES_SHUTTING_DOWN;
822 IngestModuleTier moduleTier = ingestModuleTiers.get(moduleTierIndex);
823
824 Optional<DataSourceIngestPipeline> dataSourcePipeline = moduleTier.getDataSourceIngestPipeline();
825 if (dataSourcePipeline.isPresent()) {
826 shutDownIngestModulePipeline(dataSourcePipeline.get());
827 }
828
829 for (FileIngestPipeline pipeline : moduleTier.getFileIngestPipelines()) {
830 shutDownIngestModulePipeline(pipeline);
831 }
832
833 Optional<DataArtifactIngestPipeline> dataArtifactPipeline = moduleTier.getDataArtifactIngestPipeline();
834 if (dataArtifactPipeline.isPresent()) {
835 shutDownIngestModulePipeline(dataArtifactPipeline.get());
836 }
837
838 Optional<AnalysisResultIngestPipeline> analysisResultPipeline = moduleTier.getAnalysisResultIngestPipeline();
839 if (analysisResultPipeline.isPresent()) {
840 shutDownIngestModulePipeline(analysisResultPipeline.get());
841 }
842
843 finishAllProgressBars();
844 }
845
851 private <T extends IngestTask> void shutDownIngestModulePipeline(IngestPipeline<T> pipeline) {
852 if (pipeline.isRunning()) {
853 List<IngestModuleError> errors = new ArrayList<>();
854 errors.addAll(pipeline.shutDown());
855 if (!errors.isEmpty()) {
856 logIngestModuleErrors(errors);
857 }
858 }
859 }
860
864 private void shutDown() {
865 logInfoMessage("Finished all ingest tasks for ingest job"); //NON-NLS
866 try {
867 if (casDbingestJobInfo != null) {
868 if (jobCancelled) {
869 casDbingestJobInfo.setIngestJobStatus(IngestJobStatusType.CANCELLED);
870 } else {
871 casDbingestJobInfo.setIngestJobStatus(IngestJobStatusType.COMPLETED);
872 }
873 casDbingestJobInfo.setEndDateTime(new Date());
874 }
875 } catch (TskCoreException ex) {
876 logErrorMessage(Level.WARNING, "Failed to set job end date in case database", ex);
877 }
878
879 ingestJob.notifyIngestPipelinesShutDown();
880 }
881
887 DataSourceIngestPipeline.DataSourcePipelineModule getCurrentDataSourceIngestModule() {
888 Optional<DataSourceIngestPipeline> pipeline = getCurrentDataSourceIngestPipelines();
889 if (pipeline.isPresent()) {
890 return (DataSourceIngestPipeline.DataSourcePipelineModule) pipeline.get().getCurrentlyRunningModule();
891 } else {
892 return null;
893 }
894 }
895
909 void cancelCurrentDataSourceIngestModule() {
910 currentDataSourceIngestModuleCancelled = true;
911 }
912
929 boolean currentDataSourceIngestModuleIsCancelled() {
930 return currentDataSourceIngestModuleCancelled;
931 }
932
950 void currentDataSourceIngestModuleCancellationCompleted(String moduleDisplayName) {
951 currentDataSourceIngestModuleCancelled = false;
952 cancelledDataSourceIngestModules.add(moduleDisplayName);
953 if (usingNetBeansGUI && !jobCancelled) {
954 try {
955 // use invokeAndWait to ensure synchronous behavior.
956 // See JIRA-8298 for more information.
957 SwingUtilities.invokeAndWait(() -> {
966 dataSourceIngestProgressBar.finish();
967 dataSourceIngestProgressBar = null;
968 startDataSourceIngestProgressBar();
969 });
970 } catch (InvocationTargetException | InterruptedException ex) {
971 logger.log(Level.WARNING, "Cancellation worker cancelled.", ex);
972 }
973 }
974 }
975
986 void cancel(IngestJob.CancellationReason reason) {
987 jobCancelled = true;
988 cancellationReason = reason;
989 displayCancellingProgressMessages();
990 taskScheduler.cancelPendingFileTasksForIngestJob(getIngestJobId());
991 synchronized (threadRegistrationLock) {
992 for (Thread thread : pausedIngestThreads) {
993 thread.interrupt();
994 }
995 pausedIngestThreads.clear();
996 }
997 checkForTierCompleted(moduleTierIndex);
998 }
999
1007 boolean isCancelled() {
1008 return jobCancelled;
1009 }
1010
1016 IngestJob.CancellationReason getCancellationReason() {
1017 return cancellationReason;
1018 }
1019
1027 private void startDataSourceIngestProgressBar() {
1028 if (usingNetBeansGUI) {
1029 SwingUtilities.invokeLater(() -> {
1030 dataSourceIngestProgressBar = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "IngestJob.progress.dataSourceIngest.initialDisplayName", ingestJob.getDataSource().getName()), new Cancellable() {
1031 @Override
1032 public boolean cancel() {
1033 /*
1034 * The user has already pressed the cancel button on
1035 * this progress bar, and the OK button of a cancelation
1036 * confirmation dialog supplied by NetBeans. Find out
1037 * whether the user wants to cancel only the currently
1038 * executing data source ingest module or the entire
1039 * ingest job.
1040 */
1041 DataSourceIngestCancellationPanel panel = new DataSourceIngestCancellationPanel();
1042 String dialogTitle = NbBundle.getMessage(IngestJobExecutor.this.getClass(), "IngestJob.cancellationDialog.title");
1043 JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), panel, dialogTitle, JOptionPane.OK_OPTION, JOptionPane.PLAIN_MESSAGE);
1044 if (panel.cancelAllDataSourceIngestModules()) {
1045 new Thread(() -> {
1046 IngestJobExecutor.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
1047 }).start();
1048 } else {
1049 new Thread(() -> {
1050 IngestJobExecutor.this.cancelCurrentDataSourceIngestModule();
1051 }).start();
1052 }
1053 return true;
1054 }
1055 });
1056 dataSourceIngestProgressBar.start();
1057 dataSourceIngestProgressBar.switchToIndeterminate();
1058 });
1059 }
1060 }
1061
1068 void changeDataSourceIngestProgressBarTitle(String title) {
1069 if (usingNetBeansGUI && !jobCancelled) {
1070 SwingUtilities.invokeLater(() -> {
1071 if (dataSourceIngestProgressBar != null) {
1072 dataSourceIngestProgressBar.setDisplayName(title);
1073 }
1074 });
1075 }
1076 }
1077
1082 void switchDataSourceIngestProgressBarToIndeterminate() {
1083 if (usingNetBeansGUI && !jobCancelled) {
1084 SwingUtilities.invokeLater(() -> {
1085 if (dataSourceIngestProgressBar != null) {
1086 dataSourceIngestProgressBar.switchToIndeterminate();
1087 dataSourceIngestProgressBar.progress(""); //NON-NLS
1088 }
1089 });
1090 }
1091 }
1092
1099 void switchDataSourceIngestProgressBarToDeterminate(int workUnitsToDo) {
1100 if (usingNetBeansGUI && !jobCancelled) {
1101 SwingUtilities.invokeLater(() -> {
1102 if (dataSourceIngestProgressBar != null) {
1103 dataSourceIngestProgressBar.switchToDeterminate(workUnitsToDo);
1104 }
1105 });
1106 }
1107 }
1108
1126 void updateDataSourceIngestProgressBar(String newText, int workUnitsDone) {
1127 if (usingNetBeansGUI && !jobCancelled) {
1128 SwingUtilities.invokeLater(() -> {
1129 if (dataSourceIngestProgressBar != null) {
1130 dataSourceIngestProgressBar.progress(newText, workUnitsDone);
1131 }
1132 });
1133 }
1134 }
1135
1142 void updateDataSourceIngestProgressBarText(String newText) {
1143 if (usingNetBeansGUI && !jobCancelled) {
1144 SwingUtilities.invokeLater(() -> {
1145 if (dataSourceIngestProgressBar != null) {
1146 dataSourceIngestProgressBar.progress(newText);
1147 }
1148 });
1149 }
1150 }
1151
1165 void updateDataSourceIngestProgressBar(int workUnitsDone) {
1166 if (usingNetBeansGUI && !jobCancelled) {
1167 SwingUtilities.invokeLater(() -> {
1168 if (dataSourceIngestProgressBar != null) {
1169 dataSourceIngestProgressBar.progress("", workUnitsDone);
1170 }
1171 });
1172 }
1173 }
1174
1186 private void startFileIngestProgressBar(boolean useDeterminateMode) {
1187 if (usingNetBeansGUI) {
1188 SwingUtilities.invokeLater(() -> {
1189 fileIngestProgressBar = ProgressHandle.createHandle(NbBundle.getMessage(getClass(), "IngestJob.progress.fileIngest.displayName", ingestJob.getDataSource().getName()), new Cancellable() {
1190 @Override
1191 public boolean cancel() {
1192 new Thread(() -> {
1193 IngestJobExecutor.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
1194 }).start();
1195 return true;
1196 }
1197 });
1198 if (useDeterminateMode) {
1199 fileIngestProgressBar.start((int) estimatedFilesToProcess);
1200 } else {
1201 fileIngestProgressBar.start();
1202 }
1203 });
1204 }
1205 }
1206
1212 private void switchFileIngestProgressBarToDeterminate() {
1213 if (usingNetBeansGUI) {
1214 SwingUtilities.invokeLater(() -> {
1215 if (fileIngestProgressBar != null) {
1216 fileIngestProgressBar.switchToDeterminate((int) estimatedFilesToProcess);
1217 }
1218 });
1219 }
1220 }
1221
1229 private void updateFileProgressBarForFileTaskStarted(String fileName) {
1230 if (usingNetBeansGUI && !jobCancelled) {
1231 SwingUtilities.invokeLater(() -> {
1232 /*
1233 * If processedFiles exceeds estimatedFilesToProcess, i.e., the
1234 * max work units set for the progress bar, the progress bar
1235 * will go into an infinite loop throwing
1236 * IllegalArgumentExceptions in the EDT (NetBeans bug). Also, a
1237 * check-then-act race condition needs to be avoided here. This
1238 * can be done without guarding processedFiles and
1239 * estimatedFilesToProcess with the same lock because
1240 * estimatedFilesToProcess does not change after it is used to
1241 * switch the progress bar to determinate mode.
1242 */
1243 long processedFilesCapture = processedFiles;
1244 if (processedFilesCapture <= estimatedFilesToProcess) {
1245 fileIngestProgressBar.progress(fileName, (int) processedFilesCapture);
1246 } else {
1247 fileIngestProgressBar.progress(fileName, (int) estimatedFilesToProcess);
1248 }
1249 filesInProgress.add(fileName);
1250 });
1251 }
1252 }
1253
1262 private void updateFileProgressBarForFileTaskCompleted(String completedFileName) {
1263 if (usingNetBeansGUI && !jobCancelled) {
1264 SwingUtilities.invokeLater(() -> {
1265 filesInProgress.remove(completedFileName);
1266 /*
1267 * Display the name of another file in progress, or the empty
1268 * string if there are none.
1269 */
1270 if (filesInProgress.size() > 0) {
1271 fileIngestProgressBar.progress(filesInProgress.get(0));
1272 } else {
1273 fileIngestProgressBar.progress(""); // NON-NLS
1274 }
1275 });
1276 }
1277 }
1278
1285 private void startDataArtifactIngestProgressBar() {
1286 if (usingNetBeansGUI) {
1287 SwingUtilities.invokeLater(() -> {
1288 artifactIngestProgressBar = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "IngestJob.progress.dataArtifactIngest.displayName", ingestJob.getDataSource().getName()), new Cancellable() {
1289 @Override
1290 public boolean cancel() {
1291 new Thread(() -> {
1292 IngestJobExecutor.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
1293 }).start();
1294 return true;
1295 }
1296 });
1297 artifactIngestProgressBar.start();
1298 artifactIngestProgressBar.switchToIndeterminate();
1299 });
1300 }
1301 }
1302
1309 @NbBundle.Messages({
1310 "# {0} - data source name",
1311 "IngestJob_progress_analysisResultIngest_displayName=Analyzing analysis results from {0}"
1312 })
1313 private void startAnalysisResultIngestProgressBar() {
1314 if (usingNetBeansGUI) {
1315 SwingUtilities.invokeLater(() -> {
1316 resultIngestProgressBar = ProgressHandle.createHandle(Bundle.IngestJob_progress_analysisResultIngest_displayName(ingestJob.getDataSource().getName()), new Cancellable() {
1317 @Override
1318 public boolean cancel() {
1319 new Thread(() -> {
1320 IngestJobExecutor.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
1321 }).start();
1322 return true;
1323 }
1324 });
1325 resultIngestProgressBar.start();
1326 resultIngestProgressBar.switchToIndeterminate();
1327 });
1328 }
1329 }
1330
1335 private void displayCancellingProgressMessages() {
1336 if (usingNetBeansGUI) {
1337 SwingUtilities.invokeLater(() -> {
1338 if (dataSourceIngestProgressBar != null) {
1339 dataSourceIngestProgressBar.setDisplayName(NbBundle.getMessage(getClass(), "IngestJob.progress.dataSourceIngest.initialDisplayName", ingestJob.getDataSource().getName()));
1340 dataSourceIngestProgressBar.progress(NbBundle.getMessage(getClass(), "IngestJob.progress.cancelling"));
1341 }
1342 if (fileIngestProgressBar != null) {
1343 fileIngestProgressBar.setDisplayName(NbBundle.getMessage(getClass(), "IngestJob.progress.fileIngest.displayName", ingestJob.getDataSource().getName()));
1344 fileIngestProgressBar.progress(NbBundle.getMessage(getClass(), "IngestJob.progress.cancelling"));
1345 }
1346 if (artifactIngestProgressBar != null) {
1347 artifactIngestProgressBar.setDisplayName(NbBundle.getMessage(getClass(), "IngestJob.progress.dataArtifactIngest.displayName", ingestJob.getDataSource().getName()));
1348 artifactIngestProgressBar.progress(NbBundle.getMessage(getClass(), "IngestJob.progress.cancelling"));
1349 }
1350 if (resultIngestProgressBar != null) {
1351 resultIngestProgressBar.setDisplayName(Bundle.IngestJob_progress_analysisResultIngest_displayName(ingestJob.getDataSource().getName()));
1352 resultIngestProgressBar.progress(NbBundle.getMessage(getClass(), "IngestJob.progress.cancelling"));
1353 }
1354 });
1355 }
1356 }
1357
1361 private void finishAllProgressBars() {
1362 if (usingNetBeansGUI) {
1363 SwingUtilities.invokeLater(() -> {
1364 if (dataSourceIngestProgressBar != null) {
1365 dataSourceIngestProgressBar.finish();
1366 dataSourceIngestProgressBar = null;
1367 }
1368
1369 if (fileIngestProgressBar != null) {
1370 fileIngestProgressBar.finish();
1371 fileIngestProgressBar = null;
1372 }
1373
1374 if (artifactIngestProgressBar != null) {
1375 artifactIngestProgressBar.finish();
1376 artifactIngestProgressBar = null;
1377 }
1378
1379 if (resultIngestProgressBar != null) {
1380 resultIngestProgressBar.finish();
1381 resultIngestProgressBar = null;
1382 }
1383 });
1384 }
1385 }
1386
1393 private void logInfoMessage(String message) {
1394 logger.log(Level.INFO, String.format("%s (data source = %s, data source object ID = %d, job ID = %d)", message, ingestJob.getDataSource().getName(), ingestJob.getDataSource().getId(), getIngestJobId())); //NON-NLS
1395 }
1396
1405 private void logErrorMessage(Level level, String message, Throwable throwable) {
1406 logger.log(level, String.format("%s (data source = %s, data source object ID = %d, ingest job ID = %d)", message, ingestJob.getDataSource().getName(), ingestJob.getDataSource().getId(), getIngestJobId()), throwable); //NON-NLS
1407 }
1408
1416 private void logErrorMessage(Level level, String message) {
1417 logger.log(level, String.format("%s (data source = %s, data source object ID= %d, ingest job ID %d)", message, ingestJob.getDataSource().getName(), ingestJob.getDataSource().getId(), getIngestJobId())); //NON-NLS
1418 }
1419
1425 private void logIngestModuleErrors(List<IngestModuleError> errors) {
1426 for (IngestModuleError error : errors) {
1427 logErrorMessage(Level.SEVERE, String.format("%s experienced an error during analysis", error.getModuleDisplayName()), error.getThrowable()); //NON-NLS
1428 }
1429 }
1430
1437 private void logIngestModuleErrors(List<IngestModuleError> errors, AbstractFile file) {
1438 for (IngestModuleError error : errors) {
1439 logErrorMessage(Level.SEVERE, String.format("%s experienced an error during analysis while processing file %s (object ID = %d)", error.getModuleDisplayName(), file.getName(), file.getId()), error.getThrowable()); //NON-NLS
1440 }
1441 }
1442
1448 Optional<List<FileIngestPipeline>> getCurrentFileIngestPipelines() {
1449 // Make a local copy in case the tier increments
1450 int currentModuleTierIndex = moduleTierIndex;
1451 if (currentModuleTierIndex < ingestModuleTiers.size()) {
1452 return Optional.of(ingestModuleTiers.get(currentModuleTierIndex).getFileIngestPipelines());
1453 }
1454 return Optional.empty();
1455 }
1456
1462 Optional<DataSourceIngestPipeline> getCurrentDataSourceIngestPipelines() {
1463 // Make a local copy in case the tier increments
1464 int currentModuleTierIndex = moduleTierIndex;
1465 if (currentModuleTierIndex < ingestModuleTiers.size()) {
1466 return ingestModuleTiers.get(currentModuleTierIndex).getDataSourceIngestPipeline();
1467 }
1468 return Optional.empty();
1469 }
1470
1471
1481 @Messages({
1482 "IngestJobExecutor_progress_snapshot_currentTier_shutDown_modifier=shut down",
1483 "# {0} - tier number",
1484 "# {1} - job state modifer",
1485 "IngestJobExecutor_progress_snapshot_currentTier=Tier {0} {1}"
1486 })
1487 IngestJobProgressSnapshot getIngestJobProgressSnapshot(boolean includeIngestTasksSnapshot) {
1488 /*
1489 * Determine whether file ingest is running at the time of this snapshot
1490 * and determine the earliest file ingest module pipeline start time, if
1491 * file ingest was started at all.
1492 */
1493 boolean fileIngestRunning = false;
1494 Date fileIngestStartTime = null;
1495 Optional<List<FileIngestPipeline>> fileIngestPipelines = getCurrentFileIngestPipelines();
1496 if (!fileIngestPipelines.isPresent()) {
1497 // If there are no currently running pipelines, use the original set.
1498 fileIngestPipelines = Optional.of(ingestModuleTiers.get(0).getFileIngestPipelines());
1499 }
1500 for (FileIngestPipeline pipeline : fileIngestPipelines.get()) {
1501 if (pipeline.isRunning()) {
1502 fileIngestRunning = true;
1503 }
1504 Date pipelineStartTime = pipeline.getStartTime();
1505 if (pipelineStartTime != null && (fileIngestStartTime == null || pipelineStartTime.before(fileIngestStartTime))) {
1506 fileIngestStartTime = pipelineStartTime;
1507 }
1508 }
1509
1510 long processedFilesCount = 0;
1511 long estimatedFilesToProcessCount = 0;
1512 long snapShotTime = new Date().getTime();
1513 IngestTasksScheduler.IngestTasksSnapshot tasksSnapshot = null;
1514 if (includeIngestTasksSnapshot) {
1515 processedFilesCount = processedFiles;
1516 estimatedFilesToProcessCount = estimatedFilesToProcess;
1517 snapShotTime = new Date().getTime();
1518 tasksSnapshot = taskScheduler.getTasksSnapshotForJob(getIngestJobId());
1519 }
1520 return new IngestJobProgressSnapshot(
1521 ingestJob.getDataSource().getName(),
1522 getIngestJobId(),
1523 createTime,
1524 Bundle.IngestJobExecutor_progress_snapshot_currentTier(moduleTierIndex, jobState.equals(IngestJobState.PIPELINES_SHUTTING_DOWN) ? Bundle.IngestJobExecutor_progress_snapshot_currentTier_shutDown_modifier() : ""),
1525 getCurrentDataSourceIngestModule(),
1526 fileIngestRunning,
1527 fileIngestStartTime,
1528 jobCancelled,
1529 cancellationReason,
1530 cancelledDataSourceIngestModules,
1531 processedFilesCount,
1532 estimatedFilesToProcessCount,
1533 snapShotTime,
1534 tasksSnapshot);
1535 }
1536
1543 void registerPausedIngestThread(Thread thread) {
1544 synchronized (threadRegistrationLock) {
1545 pausedIngestThreads.add(thread);
1546 }
1547 }
1548
1555 void unregisterPausedIngestThread(Thread thread) {
1556 synchronized (threadRegistrationLock) {
1557 pausedIngestThreads.remove(thread);
1558 }
1559 }
1560
1561}
synchronized static Logger getLogger(String name)
Definition Logger.java:124

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