Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
PhotoRecCarverFileIngestModule.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2011-2018 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.modules.photoreccarver;
20
21import java.io.File;
22import java.io.IOException;
23import java.lang.ProcessBuilder.Redirect;
24import java.nio.file.DirectoryStream;
25import java.nio.file.FileAlreadyExistsException;
26import java.nio.file.Files;
27import java.nio.file.Path;
28import java.nio.file.Paths;
29import java.text.DateFormat;
30import java.text.SimpleDateFormat;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Date;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Map;
38import java.util.Set;
39import java.util.concurrent.ConcurrentHashMap;
40import java.util.concurrent.atomic.AtomicLong;
41import java.util.logging.Level;
42import java.util.stream.Collectors;
43import org.openide.modules.InstalledFileLocator;
44import org.openide.util.NbBundle;
45import org.sleuthkit.autopsy.casemodule.Case;
46import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
47import org.sleuthkit.autopsy.coreutils.ExecUtil;
48import org.sleuthkit.autopsy.coreutils.FileUtil;
49import org.sleuthkit.autopsy.coreutils.Logger;
50import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
51import org.sleuthkit.autopsy.coreutils.PlatformUtil;
52import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
53import org.sleuthkit.autopsy.datamodel.ContentUtils;
54import org.sleuthkit.autopsy.ingest.FileIngestModule;
55import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator;
56import org.sleuthkit.autopsy.ingest.IngestJobContext;
57import org.sleuthkit.autopsy.ingest.IngestMessage;
58import org.sleuthkit.autopsy.ingest.IngestModule;
59import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
60import org.sleuthkit.autopsy.ingest.IngestMonitor;
61import org.sleuthkit.autopsy.ingest.IngestServices;
62import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
63import org.sleuthkit.autopsy.ingest.ProcTerminationCode;
64import org.sleuthkit.datamodel.AbstractFile;
65import org.sleuthkit.datamodel.Content;
66import org.sleuthkit.datamodel.DataSource;
67import org.sleuthkit.datamodel.LayoutFile;
68import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
69import org.sleuthkit.datamodel.TskCoreException;
70import org.sleuthkit.datamodel.TskData;
71import org.sleuthkit.datamodel.VirtualDirectory;
72
77@NbBundle.Messages({
78 "PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing",
79 "PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See 'Shared Drive Authentication' in Autopsy help.",
80 "# {0} - output directory name", "cannotCreateOutputDir.message=Unable to create output directory: {0}.",
81 "unallocatedSpaceProcessingSettingsError.message=The selected file ingest filter ignores unallocated space. This module carves unallocated space. Please choose a filter which does not ignore unallocated space or disable this module.",
82 "unsupportedOS.message=PhotoRec module is supported on Windows platforms only.",
83 "missingExecutable.message=Unable to locate PhotoRec executable.",
84 "cannotRunExecutable.message=Unable to execute PhotoRec.",
85 "PhotoRecIngestModule.nonHostnameUNCPathUsed=PhotoRec cannot operate with a UNC path containing IP addresses."
86})
87final class PhotoRecCarverFileIngestModule implements FileIngestModule {
88
89 static final boolean DEFAULT_CONFIG_KEEP_CORRUPTED_FILES = false;
90 static final PhotoRecCarverIngestJobSettings.ExtensionFilterOption DEFAULT_CONFIG_EXTENSION_FILTER
91 = PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER;
92
93 static final boolean DEFAULT_CONFIG_INCLUDE_ELSE_EXCLUDE = false;
94
95 private static final String PHOTOREC_TEMP_SUBDIR = "PhotoRec Carver"; // NON-NLS Note that we need the space in this dir name (JIRA-6878)
96 private static final String PHOTOREC_DIRECTORY = "photorec_exec"; //NON-NLS
97 private static final String PHOTOREC_SUBDIRECTORY = "bin"; //NON-NLS
98 private static final String PHOTOREC_EXECUTABLE = "photorec_win.exe"; //NON-NLS
99 private static final String PHOTOREC_LINUX_EXECUTABLE = "photorec";
100 private static final String PHOTOREC_RESULTS_BASE = "results"; //NON-NLS
101 private static final String PHOTOREC_RESULTS_EXTENDED = "results.1"; //NON-NLS
102 private static final String PHOTOREC_REPORT = "report.xml"; //NON-NLS
103 private static final String LOG_FILE = "run_log.txt"; //NON-NLS
104 private static final String SEP = System.getProperty("line.separator");
105 private static final Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName());
106 private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
107 private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
108 private static final Map<Long, WorkingPaths> pathsByJob = new ConcurrentHashMap<>();
109 private IngestJobContext context;
110 private Path rootOutputDirPath;
111 private Path rootTempDirPath;
112 private File executableFile;
113 private IngestServices services;
114 private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities();
115 private final PhotoRecCarverIngestJobSettings settings;
116 private String optionsString;
117 private long jobId;
118
119 private static class IngestJobTotals {
120
121 private final AtomicLong totalItemsRecovered = new AtomicLong(0);
122 private final AtomicLong totalItemsWithErrors = new AtomicLong(0);
123 private final AtomicLong totalWritetime = new AtomicLong(0);
124 private final AtomicLong totalParsetime = new AtomicLong(0);
125 }
126
132 PhotoRecCarverFileIngestModule(PhotoRecCarverIngestJobSettings settings) {
133 this.settings = settings;
134 }
135
144 private String getPhotorecOptions(PhotoRecCarverIngestJobSettings settings) {
145 List<String> toRet = new ArrayList<String>();
146
147 if (settings.isKeepCorruptedFiles()) {
148 toRet.addAll(Arrays.asList("options", "keep_corrupted_file"));
149 }
150
151 if (settings.getExtensionFilterOption()
152 != PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER) {
153
154 // add the file opt menu item
155 toRet.add("fileopt");
156
157 String enable = "enable";
158 String disable = "disable";
159
160 // if we are including file extensions, then we are excluding
161 // everything else and vice-versa.
162 String everythingEnable = settings.getExtensionFilterOption()
163 == PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE
164 ? disable : enable;
165
166 toRet.addAll(Arrays.asList("everything", everythingEnable));
167
168 final String itemEnable = settings.getExtensionFilterOption()
169 == PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE
170 ? enable : disable;
171
172 settings.getExtensions().forEach((extension) -> {
173 toRet.addAll(Arrays.asList(extension, itemEnable));
174 });
175 }
176
177 toRet.add("search");
178 return String.join(",", toRet);
179 }
180
181 private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) {
182 IngestJobTotals totals = totalsForIngestJobs.get(ingestJobId);
183 if (totals == null) {
184 totals = new PhotoRecCarverFileIngestModule.IngestJobTotals();
185 totalsForIngestJobs.put(ingestJobId, totals);
186 }
187 return totals;
188 }
189
190 private static synchronized void initTotalsForIngestJob(long ingestJobId) {
191 IngestJobTotals totals = new PhotoRecCarverFileIngestModule.IngestJobTotals();
192 totalsForIngestJobs.put(ingestJobId, totals);
193 }
194
198 @Override
199 @NbBundle.Messages({
200 "# {0} - extensions",
201 "PhotoRecCarverFileIngestModule_startUp_invalidExtensions_description=The following extensions are invalid: {0}",
202 "PhotoRecCarverFileIngestModule_startUp_noExtensionsProvided_description=No extensions provided for PhotoRec to carve."
203 })
204 public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
205 // validate settings
206 if (this.settings.getExtensionFilterOption() != PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER) {
207 if (this.settings.getExtensions().isEmpty()
208 && this.settings.getExtensionFilterOption() == PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE) {
209
211 Bundle.PhotoRecCarverFileIngestModule_startUp_noExtensionsProvided_description());
212 }
213
214 List<String> invalidExtensions = this.settings.getExtensions().stream()
215 .filter((ext) -> !PhotoRecCarverFileOptExtensions.isValidExtension(ext))
216 .collect(Collectors.toList());
217
218 if (!invalidExtensions.isEmpty()) {
220 Bundle.PhotoRecCarverFileIngestModule_startUp_invalidExtensions_description(
221 String.join(",", invalidExtensions)));
222 }
223 }
224
225 this.optionsString = getPhotorecOptions(this.settings);
226
227 this.context = context;
228 this.services = IngestServices.getInstance();
229 this.jobId = this.context.getJobId();
230
231 // If the global unallocated space processing setting and the module
232 // process unallocated space only setting are not in sych, throw an
233 // exception. Although the result would not be incorrect, it would be
234 // unfortunate for the user to get an accidental no-op for this module.
235 if (!this.context.processingUnallocatedSpace()) {
236 throw new IngestModule.IngestModuleException(Bundle.unallocatedSpaceProcessingSettingsError_message());
237 }
238
239 this.rootOutputDirPath = createModuleOutputDirectoryForCase();
240 this.rootTempDirPath = createTempOutputDirectoryForCase();
241
242 //Set photorec executable directory based on operating system.
243 executableFile = locateExecutable();
244
245 if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) {
246 try {
247 // The first instance creates an output subdirectory with a date and time stamp
248 DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS"); // NON-NLS
249 Date date = new Date();
250 String folder = this.context.getDataSource().getId() + "_" + dateFormat.format(date);
251 Path outputDirPath = Paths.get(this.rootOutputDirPath.toAbsolutePath().toString(), folder);
252 Files.createDirectories(outputDirPath);
253
254 // A temp subdirectory is also created as a location for writing unallocated space files to disk.
255 Path tempDirPath = Paths.get(this.rootTempDirPath.toString(), folder);
256 Files.createDirectory(tempDirPath);
257
258 // Save the directories for the current job.
259 PhotoRecCarverFileIngestModule.pathsByJob.put(this.jobId, new WorkingPaths(outputDirPath, tempDirPath));
260
261 // Initialize job totals
262 initTotalsForIngestJob(jobId);
263 } catch (SecurityException | IOException | UnsupportedOperationException ex) {
264 throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
265 }
266 }
267 }
268
272 @Override
273 public IngestModule.ProcessResult process(AbstractFile file) {
274 // Skip everything except unallocated space files.
275 if (file.getType() != TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
277 }
278
279 // Safely get a reference to the totalsForIngestJobs object
280 IngestJobTotals totals = getTotalsForIngestJobs(jobId);
281
282 Path tempFilePath = null;
283 try {
284 // Verify initialization succeeded.
285 if (null == this.executableFile) {
286 logger.log(Level.SEVERE, "PhotoRec carver called after failed start up"); // NON-NLS
288 }
289
290 // Check that we have roughly enough disk space left to complete the operation
291 // Some network drives always return -1 for free disk space.
292 // In this case, expect enough space and move on.
293 long freeDiskSpace = IngestServices.getInstance().getFreeDiskSpace();
294 if ((freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && ((file.getSize() * 1.2) > freeDiskSpace)) {
295 logger.log(Level.SEVERE, "PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space.", // NON-NLS
296 new Object[]{file.getName(), PhotoRecCarverIngestModuleFactory.getModuleName()}); // NON-NLS
297 MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.UnableToCarve", file.getName()),
298 NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.NotEnoughDiskSpace"));
300 }
301 if (this.context.fileIngestIsCancelled() == true) {
302 // if it was cancelled by the user, result is OK
303 logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
304 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser"));
306 }
307
308 // Write the file to disk.
309 long writestart = System.currentTimeMillis();
310 WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.jobId);
311 tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName());
312 ContentUtils.writeToFile(file, tempFilePath.toFile(), context::fileIngestIsCancelled);
313
314 if (this.context.fileIngestIsCancelled() == true) {
315 // if it was cancelled by the user, result is OK
316 logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
317 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser"));
319 }
320
321 // Create a subdirectory for this file.
322 Path outputDirPath = Paths.get(paths.getOutputDirPath().toString(), file.getName());
323 Files.createDirectory(outputDirPath);
324 File log = new File(Paths.get(outputDirPath.toString(), LOG_FILE).toString()); //NON-NLS
325
326 // Scan the file with Unallocated Carver.
327 ProcessBuilder processAndSettings = new ProcessBuilder(
328 executableFile.toString(),
329 "/d", // NON-NLS
330 outputDirPath.toAbsolutePath().toString() + File.separator + PHOTOREC_RESULTS_BASE,
331 "/cmd", // NON-NLS
332 tempFilePath.toFile().toString());
333
334 processAndSettings.command().add(this.optionsString);
335
336 // Add environment variable to force PhotoRec to run with the same permissions Autopsy uses
337 processAndSettings.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
338 processAndSettings.redirectErrorStream(true);
339 processAndSettings.redirectOutput(Redirect.appendTo(log));
340
341 FileIngestModuleProcessTerminator terminator = new FileIngestModuleProcessTerminator(this.context, true);
342 int exitValue = ExecUtil.execute(processAndSettings, terminator);
343
344 if (this.context.fileIngestIsCancelled() == true) {
345 // if it was cancelled by the user, result is OK
346 cleanup(outputDirPath, tempFilePath);
347 logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
348 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser"));
350 } else if (terminator.getTerminationCode() == ProcTerminationCode.TIME_OUT) {
351 cleanup(outputDirPath, tempFilePath);
352 String msg = NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.processTerminated") + file.getName(); // NON-NLS
353 MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.moduleError"), msg); // NON-NLS
354 logger.log(Level.SEVERE, msg);
356 } else if (0 != exitValue) {
357 // if it failed or was cancelled by timeout, result is ERROR
358 cleanup(outputDirPath, tempFilePath);
359 totals.totalItemsWithErrors.incrementAndGet();
360 logger.log(Level.SEVERE, "PhotoRec carver returned error exit value = {0} when scanning {1}", // NON-NLS
361 new Object[]{exitValue, file.getName()}); // NON-NLS
362 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.error.exitValue", // NON-NLS
363 new Object[]{exitValue, file.getName()}));
365 }
366
367 // Move carver log file to avoid placement into Autopsy results. PhotoRec appends ".1" to the folder name.
368 java.io.File oldAuditFile = new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_RESULTS_EXTENDED, PHOTOREC_REPORT).toString()); //NON-NLS
369 java.io.File newAuditFile = new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_REPORT).toString()); //NON-NLS
370 oldAuditFile.renameTo(newAuditFile);
371
372 if (this.context.fileIngestIsCancelled() == true) {
373 // if it was cancelled by the user, result is OK
374 logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
375 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser"));
377 }
378 Path pathToRemove = Paths.get(outputDirPath.toAbsolutePath().toString());
379 try (DirectoryStream<Path> stream = Files.newDirectoryStream(pathToRemove)) {
380 for (Path entry : stream) {
381 if (Files.isDirectory(entry)) {
382 FileUtil.deleteDir(new File(entry.toString()));
383 }
384 }
385 }
386 long writedelta = (System.currentTimeMillis() - writestart);
387 totals.totalWritetime.addAndGet(writedelta);
388
389 // Now that we've cleaned up the folders and data files, parse the xml output file to add carved items into the database
390 long calcstart = System.currentTimeMillis();
391 PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath);
392 if (this.context.fileIngestIsCancelled() == true) {
393 // if it was cancelled by the user, result is OK
394 logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
395 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser"));
397 }
398 List<LayoutFile> carvedItems = parser.parse(newAuditFile, file, context);
399 long calcdelta = (System.currentTimeMillis() - calcstart);
400 totals.totalParsetime.addAndGet(calcdelta);
401 if (carvedItems != null && !carvedItems.isEmpty()) { // if there were any results from carving, add the unallocated carving event to the reports list.
402 totals.totalItemsRecovered.addAndGet(carvedItems.size());
403 context.addFilesToJob(new ArrayList<>(carvedItems));
404
405 // Fire events for all virtual directory parents that may have just been created
406 try {
407 List<AbstractFile> virtualParentDirs = getVirtualDirectoryParents(carvedItems);
408 for (AbstractFile virtualDir : virtualParentDirs) {
409 services.fireModuleContentEvent(new ModuleContentEvent(virtualDir));
410 }
411 } catch (TskCoreException ex) {
412 logger.log(Level.WARNING, "Error collecting carved file parent directories", ex);
413 }
414 services.fireModuleContentEvent(new ModuleContentEvent(carvedItems.get(0))); // fire an event to update the tree
415 }
416 } catch (ReadContentInputStreamException ex) {
417 totals.totalItemsWithErrors.incrementAndGet();
418 logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d) with the PhotoRec carver.", file.getName(), file.getId()), ex); // NON-NLS
419 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.error.msg", file.getName()));
421 } catch (IOException ex) {
422 totals.totalItemsWithErrors.incrementAndGet();
423 logger.log(Level.SEVERE, String.format("Error writing or processing file '%s' (id=%d) to '%s' with the PhotoRec carver.", file.getName(), file.getId(), tempFilePath), ex); // NON-NLS
424 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.error.msg", file.getName()));
426 } finally {
427 if (null != tempFilePath && Files.exists(tempFilePath)) {
428 // Get rid of the unallocated space file.
429 tempFilePath.toFile().delete();
430 }
431 }
433
434 }
435
448 private List<AbstractFile> getVirtualDirectoryParents(List<LayoutFile> layoutFiles) throws TskCoreException {
449 // Keep track of which parent IDs we've already looked at to avoid unneccessary database lookups
450 Set<Long> processedParentIds = new HashSet<>();
451
452 // For each layout file, go up its parent structure until we:
453 // - Find a parent that we've already looked at
454 // - Find a parent that is not a normal virtual directory
455 // Add all new parents to the list.
456 List<AbstractFile> parentFiles = new ArrayList<>();
457 for (LayoutFile file : layoutFiles) {
458 AbstractFile currentFile = file;
459 while (currentFile.getParentId().isPresent() && !processedParentIds.contains(currentFile.getParentId().get())) {
460 Content parent = currentFile.getParent();
461 processedParentIds.add(parent.getId());
462 if (! (parent instanceof VirtualDirectory)
463 || (currentFile instanceof DataSource)) {
464 // Stop if we hit a non-virtual directory
465 break;
466 }
467
468 // Move up to the next level and save the current virtual directory to the list.
469 currentFile = (AbstractFile)parent;
470 parentFiles.add(currentFile);
471 }
472 }
473 return parentFiles;
474 }
475
476 private void cleanup(Path outputDirPath, Path tempFilePath) {
477 // cleanup the output path
478 FileUtil.deleteDir(new File(outputDirPath.toString()));
479 if (null != tempFilePath && Files.exists(tempFilePath)) {
480 tempFilePath.toFile().delete();
481 }
482 }
483
484 private static synchronized void postSummary(long jobId) {
485 IngestJobTotals jobTotals = totalsForIngestJobs.remove(jobId);
486
487 StringBuilder detailsSb = new StringBuilder();
488 //details
489 detailsSb.append("<table border='0' cellpadding='4' width='280'>"); //NON-NLS
490
491 detailsSb.append("<tr><td>") //NON-NLS
492 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.complete.numberOfCarved"))
493 .append("</td>"); //NON-NLS
494 detailsSb.append("<td>").append(jobTotals.totalItemsRecovered.get()).append("</td></tr>"); //NON-NLS
495
496 detailsSb.append("<tr><td>") //NON-NLS
497 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.complete.numberOfErrors"))
498 .append("</td>"); //NON-NLS
499 detailsSb.append("<td>").append(jobTotals.totalItemsWithErrors.get()).append("</td></tr>"); //NON-NLS
500
501 detailsSb.append("<tr><td>") //NON-NLS
502 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.complete.totalWritetime"))
503 .append("</td><td>").append(jobTotals.totalWritetime.get()).append("</td></tr>\n"); //NON-NLS
504 detailsSb.append("<tr><td>") //NON-NLS
505 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.complete.totalParsetime"))
506 .append("</td><td>").append(jobTotals.totalParsetime.get()).append("</td></tr>\n"); //NON-NLS
507 detailsSb.append("</table>"); //NON-NLS
508
511 PhotoRecCarverIngestModuleFactory.getModuleName(),
512 NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
513 "PhotoRecIngestModule.complete.photoRecResults"),
514 detailsSb.toString()));
515
516 }
517
521 @Override
522 public void shutDown() {
523 if (this.context != null && refCounter.decrementAndGet(this.jobId) == 0) {
524 try {
525 // The last instance of this module for an ingest job cleans out
526 // the working paths map entry for the job and deletes the temp dir.
527 WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.jobId);
528 FileUtil.deleteDir(new File(paths.getTempDirPath().toString()));
529 postSummary(jobId);
530 } catch (SecurityException ex) {
531 logger.log(Level.SEVERE, "Error shutting down PhotoRec carver module", ex); // NON-NLS
532 }
533 }
534 }
535
536 private static final class WorkingPaths {
537
538 private final Path outputDirPath;
539 private final Path tempDirPath;
540
541 WorkingPaths(Path outputDirPath, Path tempDirPath) {
542 this.outputDirPath = outputDirPath;
543 this.tempDirPath = tempDirPath;
544 }
545
546 Path getOutputDirPath() {
547 return this.outputDirPath;
548 }
549
550 Path getTempDirPath() {
551 return this.tempDirPath;
552 }
553 }
554
563 synchronized Path createTempOutputDirectoryForCase() throws IngestModule.IngestModuleException {
564 try {
565 Path path = Paths.get(Case.getCurrentCaseThrows().getTempDirectory(), PHOTOREC_TEMP_SUBDIR);
566 return createOutputDirectoryForCase(path);
567 } catch (NoCurrentCaseException ex) {
568 throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
569 }
570 }
571
580 synchronized Path createModuleOutputDirectoryForCase() throws IngestModule.IngestModuleException {
581 try {
582 Path path = Paths.get(Case.getCurrentCaseThrows().getModuleDirectory(), PhotoRecCarverIngestModuleFactory.getModuleName());
583 return createOutputDirectoryForCase(path);
584 } catch (NoCurrentCaseException ex) {
585 throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
586 }
587 }
588
599 private synchronized Path createOutputDirectoryForCase(Path providedPath) throws IngestModule.IngestModuleException {
600 Path path = providedPath;
601 try {
602 Files.createDirectory(path);
603 if (UNCPathUtilities.isUNC(path)) {
604 // if the UNC path is using an IP address, convert to hostname
605 path = uncPathUtilities.ipToHostName(path);
606 if (path == null) {
607 throw new IngestModule.IngestModuleException(Bundle.PhotoRecIngestModule_nonHostnameUNCPathUsed());
608 }
609 if (false == FileUtil.hasReadWriteAccess(path)) {
611 Bundle.PhotoRecIngestModule_PermissionsNotSufficient() + SEP + path.toString() + SEP
612 + Bundle.PhotoRecIngestModule_PermissionsNotSufficientSeeReference()
613 );
614 }
615 }
616 } catch (FileAlreadyExistsException ex) {
617 // No worries.
618 } catch (IOException | SecurityException | UnsupportedOperationException ex) {
619 throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
620 }
621 return path;
622 }
623
633 public static File locateExecutable() throws IngestModule.IngestModuleException {
634 File exeFile;
636 Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_SUBDIRECTORY, PHOTOREC_EXECUTABLE);
637 exeFile = InstalledFileLocator.getDefault().locate(execName.toString(), PhotoRecCarverFileIngestModule.class.getPackage().getName(), false);
638 } else {
639 exeFile = null;
640 for (String dirName: System.getenv("PATH").split(File.pathSeparator)) {
641 File testExe = new File(dirName, PHOTOREC_LINUX_EXECUTABLE);
642 if (testExe.exists()) {
643 exeFile = testExe;
644 break;
645 }
646 }
647 }
648
649 if (null == exeFile) {
650 throw new IngestModule.IngestModuleException(Bundle.missingExecutable_message());
651 }
652
653 if (!exeFile.canExecute()) {
654 throw new IngestModule.IngestModuleException(Bundle.cannotRunExecutable_message());
655 }
656
657 return exeFile;
658 }
659
660}
static int execute(ProcessBuilder processBuilder)
static boolean deleteDir(File dirPath)
Definition FileUtil.java:47
static boolean hasReadWriteAccess(Path dirPath)
synchronized static Logger getLogger(String name)
Definition Logger.java:124
synchronized static boolean isUNC(Path inputPath)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
void postMessage(final IngestMessage message)
static synchronized IngestServices getInstance()

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