package org.sleuthkit.autopsy.datasourceprocessors; /* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> 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. */ import java.io.File; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskFileRange; import org.openide.util.NbBundle.Messages; /* * A runnable that adds a raw data source to a case database. */ final class AddRawImageTask implements Runnable { private static final Logger logger = Logger.getLogger(AddRawImageTask.class.getName()); private final String deviceId; private final String imageFilePath; private final String timeZone; private final long chunkSize; private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; private boolean criticalErrorOccurred; private static final long TWO_GB = 2000000000L; /** * Constructs a runnable that adds a raw data source to a case database. * * @param deviceId An ASCII-printable identifier for the * device associated with the data source * that is intended to be unique across * multiple cases (e.g., a UUID). * @param imageFilePath Path to a Raw data source file. * @param timeZone The time zone to use when processing dates * and times for the image, obtained from * java.util.TimeZone.getID. * @param breakupChunks 2GB or not breakup. * @param progressMonitor Progress monitor for reporting * progressMonitor during processing. * @param callback Callback to call when processing is done. */ AddRawImageTask(String deviceId, String imageFilePath, String timeZone, long chunkSize, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; this.imageFilePath = imageFilePath; this.timeZone = timeZone; this.chunkSize = chunkSize; this.callback = callback; this.progressMonitor = progressMonitor; } /** * Adds a raw data source to a case database. */ @Override public void run() { /* * Process the input image file. */ progressMonitor.setIndeterminate(true); progressMonitor.setProgress(0); List<Content> newDataSources = new ArrayList<>(); List<String> errorMessages = new ArrayList<>(); addImageToCase(newDataSources, errorMessages); progressMonitor.setProgress(100); /** * Return the results via the callback passed to the constructor. */ DataSourceProcessorCallback.DataSourceProcessorResult result; if (criticalErrorOccurred) { result = DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; } else if (!errorMessages.isEmpty()) { result = DataSourceProcessorCallback.DataSourceProcessorResult.NONCRITICAL_ERRORS; } else { result = DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS; } callback.done(result, errorMessages, newDataSources); criticalErrorOccurred = false; } /** * Attempts to add the input image to the case. * * @param newDataSources If the image is added, a data source is added to * this list for eventual return to the caller via the * callback. * @param errorMessages If there are any error messages, the error messages * are added to this list for eventual return to the * caller via the callback. */ @Messages({"AddRawImageTask.progress.add.text=Adding raw image: ", "AddRawImageTask.image.critical.error.adding=Critical error adding ", "AddRawImageTask.for.device=for device ", "AddRawImageTask.image.notExisting=is not existing.", "AddRawImageTask.image.noncritical.error.adding=Non-critical error adding "}) private void addImageToCase(List<Content> dataSources, List<String> errorMessages) { progressMonitor.setProgressText(Bundle.AddRawImageTask_progress_add_text() + imageFilePath); List<String> imageFilePaths = new ArrayList<>(); SleuthkitCase caseDatabase = Case.getCurrentCase().getSleuthkitCase(); caseDatabase.acquireExclusiveLock(); File imageFile = Paths.get(imageFilePath).toFile(); if (!imageFile.exists()) { errorMessages.add(Bundle.AddRawImageTask_image_critical_error_adding() + imageFilePath + Bundle.AddRawImageTask_for_device() + deviceId + Bundle.AddRawImageTask_image_notExisting()); criticalErrorOccurred = true; return; } imageFilePaths.add(imageFilePath); try { /* * Get Image that will be added to case */ Image dataSource = caseDatabase.addImageInfo(0, imageFilePaths, timeZone); //TODO: change hard coded deviceId. dataSources.add(dataSource); List<TskFileRange> fileRanges = new ArrayList<>(); /* * Verify the size of the new image. Note that it may not be what is * expected, but at least part of it was added to the case. */ String verificationError = dataSource.verifyImageSize(); if (!verificationError.isEmpty()) { errorMessages.add(Bundle.AddRawImageTask_image_noncritical_error_adding() + imageFilePaths + Bundle.AddRawImageTask_for_device() + deviceId + ":" + verificationError); } long imageSize = dataSource.getSize(); int sequence = 0; //start byte and end byte long start = 0; if (chunkSize > 0 && imageSize >= TWO_GB) { for (double size = TWO_GB; size < dataSource.getSize(); size += TWO_GB) { fileRanges.add(new TskFileRange(start, TWO_GB, sequence)); start += TWO_GB; sequence++; } } double leftoverSize = imageSize - sequence * TWO_GB; fileRanges.add(new TskFileRange(start, (long)leftoverSize, sequence)); caseDatabase.addLayoutFiles(dataSource, fileRanges); } catch (TskCoreException ex) { errorMessages.add(Bundle.AddRawImageTask_image_critical_error_adding() + imageFilePaths + Bundle.AddRawImageTask_for_device() + deviceId + ":" + ex.getLocalizedMessage()); criticalErrorOccurred = true; } finally { caseDatabase.releaseExclusiveLock(); } } }