19 package org.sleuthkit.autopsy.ingest;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.Date;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.concurrent.CopyOnWriteArrayList;
28 import java.util.concurrent.LinkedBlockingQueue;
29 import java.util.concurrent.atomic.AtomicLong;
30 import java.util.logging.Level;
31 import javax.swing.JOptionPane;
32 import org.netbeans.api.progress.ProgressHandle;
33 import org.netbeans.api.progress.ProgressHandleFactory;
34 import org.openide.util.Cancellable;
35 import org.openide.util.NbBundle;
44 final class DataSourceIngestJob {
46 private static final Logger logger = Logger.getLogger(DataSourceIngestJob.class.getName());
52 private final IngestJob parentJob;
53 private static final AtomicLong nextJobId =
new AtomicLong(0L);
54 private final long id;
55 private final IngestJobSettings settings;
56 private final Content dataSource;
83 private final Object stageCompletionCheckLock =
new Object();
93 private final Object dataSourceIngestPipelineLock =
new Object();
94 private DataSourceIngestPipeline firstStageDataSourceIngestPipeline;
95 private DataSourceIngestPipeline secondStageDataSourceIngestPipeline;
96 private DataSourceIngestPipeline currentDataSourceIngestPipeline;
105 private final LinkedBlockingQueue<FileIngestPipeline> fileIngestPipelinesQueue =
new LinkedBlockingQueue<>();
106 private final List<FileIngestPipeline> fileIngestPipelines =
new ArrayList<>();
119 private volatile boolean currentDataSourceIngestModuleCancelled;
120 private volatile boolean cancelled;
121 private final List<String> cancelledDataSourceIngestModules =
new CopyOnWriteArrayList<>();
127 private static final IngestTasksScheduler taskScheduler = IngestTasksScheduler.getInstance();
133 private final boolean runInteractively;
139 private final Object dataSourceIngestProgressLock =
new Object();
140 private ProgressHandle dataSourceIngestProgress;
146 private final Object fileIngestProgressLock =
new Object();
147 private final List<String> filesInProgress =
new ArrayList<>();
148 private long estimatedFilesToProcess;
149 private long processedFiles;
150 private ProgressHandle fileIngestProgress;
155 private final long createTime;
169 this.parentJob = parentJob;
170 this.
id = DataSourceIngestJob.nextJobId.getAndIncrement();
171 this.dataSource = dataSource;
172 this.settings = settings;
173 this.runInteractively = runInteractively;
174 this.createTime =
new Date().getTime();
175 this.createIngestPipelines();
181 private void createIngestPipelines() {
182 List<IngestModuleTemplate> ingestModuleTemplates = this.settings.getEnabledIngestModuleTemplates();
187 Map<String, IngestModuleTemplate> dataSourceModuleTemplates =
new HashMap<>();
188 Map<String, IngestModuleTemplate> fileModuleTemplates =
new HashMap<>();
189 for (IngestModuleTemplate
template : ingestModuleTemplates) {
190 if (
template.isDataSourceIngestModuleTemplate()) {
191 dataSourceModuleTemplates.put(
template.getModuleFactory().getClass().getCanonicalName(),
template);
193 if (
template.isFileIngestModuleTemplate()) {
194 fileModuleTemplates.put(
template.getModuleFactory().getClass().getCanonicalName(),
template);
202 IngestPipelinesConfiguration pipelineConfigs = IngestPipelinesConfiguration.getInstance();
203 List<IngestModuleTemplate> firstStageDataSourceModuleTemplates = DataSourceIngestJob.getConfiguredIngestModuleTemplates(dataSourceModuleTemplates, pipelineConfigs.getStageOneDataSourceIngestPipelineConfig());
204 List<IngestModuleTemplate> fileIngestModuleTemplates = DataSourceIngestJob.getConfiguredIngestModuleTemplates(fileModuleTemplates, pipelineConfigs.getFileIngestPipelineConfig());
205 List<IngestModuleTemplate> secondStageDataSourceModuleTemplates = DataSourceIngestJob.getConfiguredIngestModuleTemplates(dataSourceModuleTemplates, pipelineConfigs.getStageTwoDataSourceIngestPipelineConfig());
212 for (IngestModuleTemplate
template : dataSourceModuleTemplates.values()) {
213 firstStageDataSourceModuleTemplates.add(
template);
215 for (IngestModuleTemplate
template : fileModuleTemplates.values()) {
216 fileIngestModuleTemplates.add(
template);
222 this.firstStageDataSourceIngestPipeline =
new DataSourceIngestPipeline(
this, firstStageDataSourceModuleTemplates);
223 this.secondStageDataSourceIngestPipeline =
new DataSourceIngestPipeline(
this, secondStageDataSourceModuleTemplates);
229 int numberOfFileIngestThreads = IngestManager.getInstance().getNumberOfFileIngestThreads();
230 for (
int i = 0; i < numberOfFileIngestThreads; ++i) {
231 FileIngestPipeline pipeline =
new FileIngestPipeline(
this, fileIngestModuleTemplates);
232 this.fileIngestPipelinesQueue.put(pipeline);
233 this.fileIngestPipelines.add(pipeline);
235 }
catch (InterruptedException ex) {
241 Thread.currentThread().interrupt();
259 private static List<IngestModuleTemplate> getConfiguredIngestModuleTemplates(Map<String, IngestModuleTemplate> ingestModuleTemplates, List<String> pipelineConfig) {
260 List<IngestModuleTemplate> templates =
new ArrayList<>();
261 for (String moduleClassName : pipelineConfig) {
262 if (ingestModuleTemplates.containsKey(moduleClassName)) {
263 templates.add(ingestModuleTemplates.remove(moduleClassName));
283 Content getDataSource() {
284 return this.dataSource;
293 boolean shouldProcessUnallocatedSpace() {
294 return this.settings.getProcessUnallocatedSpace();
302 boolean hasIngestPipeline() {
303 return this.hasFirstStageDataSourceIngestPipeline()
304 || this.hasFileIngestPipeline()
305 || this.hasSecondStageDataSourceIngestPipeline();
314 private boolean hasFirstStageDataSourceIngestPipeline() {
315 return (this.firstStageDataSourceIngestPipeline.isEmpty() ==
false);
324 private boolean hasSecondStageDataSourceIngestPipeline() {
325 return (this.secondStageDataSourceIngestPipeline.isEmpty() ==
false);
333 private boolean hasFileIngestPipeline() {
334 if (!this.fileIngestPipelines.isEmpty()) {
335 return !this.fileIngestPipelines.get(0).isEmpty();
345 List<IngestModuleError> start() {
346 List<IngestModuleError> errors = startUpIngestPipelines();
347 if (errors.isEmpty()) {
348 if (this.hasFirstStageDataSourceIngestPipeline() || this.hasFileIngestPipeline()) {
349 logger.log(Level.INFO,
"Starting first stage analysis for {0} (jobId={1})",
new Object[]{dataSource.getName(), this.id});
350 this.startFirstStage();
351 }
else if (this.hasSecondStageDataSourceIngestPipeline()) {
352 logger.log(Level.INFO,
"Starting second stage analysis for {0} (jobId={1}), no first stage configured",
new Object[]{dataSource.getName(), this.id});
353 this.startSecondStage();
365 private List<IngestModuleError> startUpIngestPipelines() {
366 List<IngestModuleError> errors =
new ArrayList<>();
369 errors.addAll(this.firstStageDataSourceIngestPipeline.startUp());
372 errors.addAll(this.secondStageDataSourceIngestPipeline.startUp());
375 for (FileIngestPipeline pipeline : this.fileIngestPipelinesQueue) {
376 errors.addAll(pipeline.startUp());
377 if (!errors.isEmpty()) {
386 while (!this.fileIngestPipelinesQueue.isEmpty()) {
387 pipeline = this.fileIngestPipelinesQueue.poll();
388 List<IngestModuleError> shutDownErrors = pipeline.shutDown();
389 if (!shutDownErrors.isEmpty()) {
390 logIngestModuleErrors(shutDownErrors);
397 logIngestModuleErrors(errors);
404 private void startFirstStage() {
405 this.stage = DataSourceIngestJob.Stages.
FIRST;
407 if (this.hasFileIngestPipeline()) {
408 synchronized (this.fileIngestProgressLock) {
409 this.estimatedFilesToProcess = this.dataSource.
accept(
new GetFilesCountVisitor());
413 if (this.runInteractively) {
417 if (this.hasFirstStageDataSourceIngestPipeline()) {
418 this.startDataSourceIngestProgressBar();
420 if (this.hasFileIngestPipeline()) {
421 this.startFileIngestProgressBar();
429 synchronized (this.dataSourceIngestPipelineLock) {
430 this.currentDataSourceIngestPipeline = this.firstStageDataSourceIngestPipeline;
436 if (this.hasFirstStageDataSourceIngestPipeline() && this.hasFileIngestPipeline()) {
437 logger.log(Level.INFO,
"Scheduling first stage data source and file level analysis tasks for {0} (jobId={1})",
new Object[]{dataSource.getName(), this.id});
438 DataSourceIngestJob.taskScheduler.scheduleIngestTasks(
this);
439 }
else if (this.hasFirstStageDataSourceIngestPipeline()) {
440 logger.log(Level.INFO,
"Scheduling first stage data source level analysis tasks for {0} (jobId={1}), no file level analysis configured",
new Object[]{dataSource.getName(), this.id});
441 DataSourceIngestJob.taskScheduler.scheduleDataSourceIngestTask(
this);
443 logger.log(Level.INFO,
"Scheduling file level analysis tasks for {0} (jobId={1}), no first stage data source level analysis configured",
new Object[]{dataSource.getName(), this.id});
444 DataSourceIngestJob.taskScheduler.scheduleFileIngestTasks(
this);
454 this.checkForStageCompleted();
461 private void startSecondStage() {
462 logger.log(Level.INFO,
"Starting second stage analysis for {0} (jobId={1})",
new Object[]{dataSource.getName(), this.id});
463 this.stage = DataSourceIngestJob.Stages.
SECOND;
464 if (this.runInteractively) {
465 this.startDataSourceIngestProgressBar();
467 synchronized (this.dataSourceIngestPipelineLock) {
468 this.currentDataSourceIngestPipeline = this.secondStageDataSourceIngestPipeline;
470 logger.log(Level.INFO,
"Scheduling second stage data source level analysis tasks for {0} (jobId={1})",
new Object[]{dataSource.getName(), this.id});
471 DataSourceIngestJob.taskScheduler.scheduleDataSourceIngestTask(
this);
477 private void startDataSourceIngestProgressBar() {
478 if (this.runInteractively) {
479 synchronized (this.dataSourceIngestProgressLock) {
480 String displayName = NbBundle.getMessage(this.getClass(),
481 "IngestJob.progress.dataSourceIngest.initialDisplayName",
483 this.dataSourceIngestProgress = ProgressHandleFactory.createHandle(displayName,
new Cancellable() {
485 public boolean cancel() {
492 DataSourceIngestCancellationPanel panel =
new DataSourceIngestCancellationPanel();
493 String dialogTitle = NbBundle.getMessage(DataSourceIngestJob.this.getClass(),
"IngestJob.cancellationDialog.title");
494 JOptionPane.showConfirmDialog(null, panel, dialogTitle, JOptionPane.OK_OPTION, JOptionPane.PLAIN_MESSAGE);
495 if (panel.cancelAllDataSourceIngestModules()) {
496 DataSourceIngestJob.this.cancel();
498 DataSourceIngestJob.this.cancelCurrentDataSourceIngestModule();
503 this.dataSourceIngestProgress.start();
504 this.dataSourceIngestProgress.switchToIndeterminate();
512 private void startFileIngestProgressBar() {
513 if (this.runInteractively) {
514 synchronized (this.fileIngestProgressLock) {
515 String displayName = NbBundle.getMessage(this.getClass(),
516 "IngestJob.progress.fileIngest.displayName",
518 this.fileIngestProgress = ProgressHandleFactory.createHandle(displayName,
new Cancellable() {
520 public boolean cancel() {
525 DataSourceIngestJob.this.cancel();
529 this.fileIngestProgress.start();
530 this.fileIngestProgress.switchToDeterminate((
int) this.estimatedFilesToProcess);
539 private void checkForStageCompleted() {
540 synchronized (this.stageCompletionCheckLock) {
541 if (DataSourceIngestJob.taskScheduler.tasksForJobAreCompleted(
this)) {
542 switch (this.stage) {
544 this.finishFirstStage();
558 private void finishFirstStage() {
559 logger.log(Level.INFO,
"Finished first stage analysis for {0} (jobId={1})",
new Object[]{dataSource.getName(), this.id});
564 List<IngestModuleError> errors =
new ArrayList<>();
565 while (!this.fileIngestPipelinesQueue.isEmpty()) {
566 FileIngestPipeline pipeline = fileIngestPipelinesQueue.poll();
567 errors.addAll(pipeline.shutDown());
569 if (!errors.isEmpty()) {
570 logIngestModuleErrors(errors);
573 if (this.runInteractively) {
576 synchronized (this.dataSourceIngestProgressLock) {
577 if (this.dataSourceIngestProgress != null) {
578 this.dataSourceIngestProgress.finish();
579 this.dataSourceIngestProgress = null;
585 synchronized (this.fileIngestProgressLock) {
586 if (this.fileIngestProgress != null) {
587 this.fileIngestProgress.finish();
588 this.fileIngestProgress = null;
596 if (!this.cancelled && this.hasSecondStageDataSourceIngestPipeline()) {
597 this.startSecondStage();
606 private void finish() {
607 logger.log(Level.INFO,
"Finished analysis for {0} (jobId={1})",
new Object[]{dataSource.getName(), this.id});
610 if (this.runInteractively) {
613 synchronized (this.dataSourceIngestProgressLock) {
614 if (this.dataSourceIngestProgress != null) {
615 this.dataSourceIngestProgress.finish();
616 this.dataSourceIngestProgress = null;
621 this.parentJob.dataSourceJobFinished(
this);
630 void process(DataSourceIngestTask task) {
632 synchronized (this.dataSourceIngestPipelineLock) {
633 if (!this.isCancelled() && !this.currentDataSourceIngestPipeline.isEmpty()) {
634 List<IngestModuleError> errors =
new ArrayList<>();
635 errors.addAll(this.currentDataSourceIngestPipeline.process(task));
636 if (!errors.isEmpty()) {
637 logIngestModuleErrors(errors);
642 if (this.runInteractively) {
647 synchronized (this.dataSourceIngestProgressLock) {
648 if (null != this.dataSourceIngestProgress) {
649 this.dataSourceIngestProgress.finish();
650 this.dataSourceIngestProgress = null;
656 DataSourceIngestJob.taskScheduler.notifyTaskCompleted(task);
657 this.checkForStageCompleted();
670 void process(FileIngestTask task)
throws InterruptedException {
672 if (!this.isCancelled()) {
673 FileIngestPipeline pipeline = this.fileIngestPipelinesQueue.take();
674 if (!pipeline.isEmpty()) {
675 AbstractFile file = task.getFile();
677 synchronized (this.fileIngestProgressLock) {
678 ++this.processedFiles;
679 if (this.runInteractively) {
683 if (this.processedFiles <= this.estimatedFilesToProcess) {
684 this.fileIngestProgress.progress(file.getName(), (int) this.processedFiles);
686 this.fileIngestProgress.progress(file.getName(), (int) this.estimatedFilesToProcess);
688 this.filesInProgress.add(file.getName());
695 List<IngestModuleError> errors =
new ArrayList<>();
696 errors.addAll(pipeline.process(task));
697 if (!errors.isEmpty()) {
698 logIngestModuleErrors(errors);
701 if (this.runInteractively && !this.cancelled) {
702 synchronized (this.fileIngestProgressLock) {
707 this.filesInProgress.remove(file.getName());
708 if (this.filesInProgress.size() > 0) {
709 this.fileIngestProgress.progress(this.filesInProgress.get(0));
711 this.fileIngestProgress.progress(
"");
716 this.fileIngestPipelinesQueue.put(pipeline);
719 DataSourceIngestJob.taskScheduler.notifyTaskCompleted(task);
720 this.checkForStageCompleted();
731 void addFiles(List<AbstractFile> files) {
732 if (DataSourceIngestJob.Stages.FIRST ==
this.stage) {
733 for (AbstractFile file : files) {
734 DataSourceIngestJob.taskScheduler.scheduleFileIngestTask(
this, file);
737 DataSourceIngestJob.logger.log(Level.SEVERE,
"Adding files during second stage not supported");
746 this.checkForStageCompleted();
755 void updateDataSourceIngestProgressBarDisplayName(String displayName) {
756 if (this.runInteractively && !this.cancelled) {
757 synchronized (this.dataSourceIngestProgressLock) {
758 this.dataSourceIngestProgress.setDisplayName(displayName);
771 void switchDataSourceIngestProgressBarToDeterminate(
int workUnits) {
772 if (this.runInteractively && !this.cancelled) {
773 synchronized (this.dataSourceIngestProgressLock) {
774 if (null != this.dataSourceIngestProgress) {
775 this.dataSourceIngestProgress.switchToDeterminate(workUnits);
786 void switchDataSourceIngestProgressBarToIndeterminate() {
787 if (this.runInteractively && !this.cancelled) {
788 synchronized (this.dataSourceIngestProgressLock) {
789 if (null != this.dataSourceIngestProgress) {
790 this.dataSourceIngestProgress.switchToIndeterminate();
802 void advanceDataSourceIngestProgressBar(
int workUnits) {
803 if (this.runInteractively && !this.cancelled) {
804 synchronized (this.dataSourceIngestProgressLock) {
805 if (null != this.dataSourceIngestProgress) {
806 this.dataSourceIngestProgress.progress(
"", workUnits);
818 void advanceDataSourceIngestProgressBar(String currentTask) {
819 if (this.runInteractively && !this.cancelled) {
820 synchronized (this.dataSourceIngestProgressLock) {
821 if (null != this.dataSourceIngestProgress) {
822 this.dataSourceIngestProgress.progress(currentTask);
836 void advanceDataSourceIngestProgressBar(String currentTask,
int workUnits) {
837 if (this.runInteractively && !this.cancelled) {
838 synchronized (this.fileIngestProgressLock) {
839 this.dataSourceIngestProgress.progress(currentTask, workUnits);
851 boolean currentDataSourceIngestModuleIsCancelled() {
852 return this.currentDataSourceIngestModuleCancelled;
861 void currentDataSourceIngestModuleCancellationCompleted(String moduleDisplayName) {
862 this.currentDataSourceIngestModuleCancelled =
false;
863 this.cancelledDataSourceIngestModules.add(moduleDisplayName);
865 if (this.runInteractively) {
873 synchronized (this.dataSourceIngestProgressLock) {
874 this.dataSourceIngestProgress.finish();
875 this.dataSourceIngestProgress = null;
876 this.startDataSourceIngestProgressBar();
886 DataSourceIngestPipeline.PipelineModule getCurrentDataSourceIngestModule() {
887 if (null != this.currentDataSourceIngestPipeline) {
888 return this.currentDataSourceIngestPipeline.getCurrentlyRunningModule();
898 void cancelCurrentDataSourceIngestModule() {
899 this.currentDataSourceIngestModuleCancelled =
true;
907 if (this.runInteractively) {
912 synchronized (this.dataSourceIngestProgressLock) {
913 if (dataSourceIngestProgress != null) {
914 final String displayName = NbBundle.getMessage(this.getClass(),
915 "IngestJob.progress.dataSourceIngest.initialDisplayName",
917 dataSourceIngestProgress.setDisplayName(
918 NbBundle.getMessage(
this.getClass(),
919 "IngestJob.progress.cancelling",
928 synchronized (this.fileIngestProgressLock) {
929 if (this.fileIngestProgress != null) {
930 final String displayName = NbBundle.getMessage(this.getClass(),
931 "IngestJob.progress.fileIngest.displayName",
933 this.fileIngestProgress.setDisplayName(
934 NbBundle.getMessage(
this.getClass(),
"IngestJob.progress.cancelling",
940 this.cancelled =
true;
946 DataSourceIngestJob.taskScheduler.cancelPendingTasksForIngestJob(
this);
947 this.checkForStageCompleted();
956 boolean isCancelled() {
957 return this.cancelled;
965 private void logIngestModuleErrors(List<IngestModuleError> errors) {
966 for (IngestModuleError error : errors) {
967 DataSourceIngestJob.logger.log(Level.SEVERE, String.format(
"%s experienced an error analyzing %s (jobId=%d)", error.getModuleDisplayName(), dataSource.
getName(), this.id), error.getModuleError());
976 Snapshot getSnapshot(
boolean getIngestTasksSnapshot) {
977 return new Snapshot(getIngestTasksSnapshot);
983 final class Snapshot {
985 private final String dataSource;
986 private final long jobId;
987 private final long jobStartTime;
988 private final long snapShotTime;
989 private final DataSourceIngestPipeline.PipelineModule dataSourceLevelIngestModule;
990 private boolean fileIngestRunning;
991 private Date fileIngestStartTime;
992 private final long processedFiles;
993 private final long estimatedFilesToProcess;
994 private final IngestTasksScheduler.IngestJobTasksSnapshot tasksSnapshot;
995 private final boolean jobCancelled;
996 private final List<String> cancelledDataSourceModules;
1002 Snapshot(
boolean getIngestTasksSnapshot) {
1003 this.dataSource = DataSourceIngestJob.this.dataSource.
getName();
1004 this.jobId = DataSourceIngestJob.this.id;
1005 this.jobStartTime = DataSourceIngestJob.this.createTime;
1006 this.dataSourceLevelIngestModule = DataSourceIngestJob.this.getCurrentDataSourceIngestModule();
1013 for (FileIngestPipeline pipeline : DataSourceIngestJob.this.fileIngestPipelines) {
1014 if (pipeline.isRunning()) {
1015 this.fileIngestRunning =
true;
1017 Date pipelineStartTime = pipeline.getStartTime();
1018 if (null != pipelineStartTime && (null == this.fileIngestStartTime || pipelineStartTime.before(
this.fileIngestStartTime))) {
1019 this.fileIngestStartTime = pipelineStartTime;
1023 this.jobCancelled = cancelled;
1024 this.cancelledDataSourceModules =
new ArrayList<>(DataSourceIngestJob.this.cancelledDataSourceIngestModules);
1026 if (getIngestTasksSnapshot) {
1027 synchronized (DataSourceIngestJob.this.fileIngestProgressLock) {
1028 this.processedFiles = DataSourceIngestJob.this.processedFiles;
1029 this.estimatedFilesToProcess = DataSourceIngestJob.this.estimatedFilesToProcess;
1030 this.snapShotTime =
new Date().getTime();
1032 this.tasksSnapshot = DataSourceIngestJob.taskScheduler.getTasksSnapshotForJob(this.jobId);
1035 this.processedFiles = 0;
1036 this.estimatedFilesToProcess = 0;
1037 this.snapShotTime =
new Date().getTime();
1038 this.tasksSnapshot = null;
1048 long getSnapshotTime() {
1049 return snapShotTime;
1058 String getDataSource() {
1078 long getJobStartTime() {
1079 return jobStartTime;
1082 DataSourceIngestPipeline.PipelineModule getDataSourceLevelIngestModule() {
1083 return this.dataSourceLevelIngestModule;
1086 boolean fileIngestIsRunning() {
1087 return this.fileIngestRunning;
1090 Date fileIngestStartTime() {
1091 return this.fileIngestStartTime;
1101 return (
double) processedFiles / ((snapShotTime - jobStartTime) / 1000);
1109 long getFilesProcessed() {
1110 return processedFiles;
1119 long getFilesEstimated() {
1120 return estimatedFilesToProcess;
1123 long getRootQueueSize() {
1124 if (null == this.tasksSnapshot) {
1127 return this.tasksSnapshot.getRootQueueSize();
1130 long getDirQueueSize() {
1131 if (null == this.tasksSnapshot) {
1134 return this.tasksSnapshot.getDirectoryTasksQueueSize();
1137 long getFileQueueSize() {
1138 if (null == this.tasksSnapshot) {
1141 return this.tasksSnapshot.getFileQueueSize();
1144 long getDsQueueSize() {
1145 if (null == this.tasksSnapshot) {
1148 return this.tasksSnapshot.getDsQueueSize();
1151 long getRunningListSize() {
1152 if (null == this.tasksSnapshot) {
1155 return this.tasksSnapshot.getRunningListSize();
1158 boolean isCancelled() {
1159 return this.jobCancelled;
1169 List<String> getCancelledDataSourceIngestModules() {
1170 return Collections.unmodifiableList(this.cancelledDataSourceModules);
public< T > T accept(ContentVisitor< T > v)