Autopsy  4.21.0
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  */
19 package org.sleuthkit.autopsy.modules.photoreccarver;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.lang.ProcessBuilder.Redirect;
24 import java.nio.file.DirectoryStream;
25 import java.nio.file.FileAlreadyExistsException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.text.DateFormat;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Date;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.atomic.AtomicLong;
41 import java.util.logging.Level;
42 import java.util.stream.Collectors;
43 import org.openide.modules.InstalledFileLocator;
44 import org.openide.util.NbBundle;
64 import org.sleuthkit.datamodel.AbstractFile;
65 import org.sleuthkit.datamodel.Content;
66 import org.sleuthkit.datamodel.DataSource;
67 import org.sleuthkit.datamodel.LayoutFile;
68 import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
69 import org.sleuthkit.datamodel.TskCoreException;
70 import org.sleuthkit.datamodel.TskData;
71 import 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 })
87 final 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 
210  throw new IngestModule.IngestModuleException(
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()) {
219  throw new IngestModule.IngestModuleException(
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) {
276  return IngestModule.ProcessResult.OK;
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
287  return IngestModule.ProcessResult.ERROR;
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"));
299  return IngestModule.ProcessResult.ERROR;
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"));
305  return IngestModule.ProcessResult.OK;
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"));
318  return IngestModule.ProcessResult.OK;
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"));
349  return IngestModule.ProcessResult.OK;
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);
355  return IngestModule.ProcessResult.ERROR;
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()}));
364  return IngestModule.ProcessResult.ERROR;
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"));
376  return IngestModule.ProcessResult.OK;
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"));
396  return IngestModule.ProcessResult.OK;
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()));
420  return IngestModule.ProcessResult.ERROR;
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()));
425  return IngestModule.ProcessResult.ERROR;
426  } finally {
427  if (null != tempFilePath && Files.exists(tempFilePath)) {
428  // Get rid of the unallocated space file.
429  tempFilePath.toFile().delete();
430  }
431  }
432  return IngestModule.ProcessResult.OK;
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 
509  IngestServices.getInstance().postMessage(IngestMessage.createMessage(
510  IngestMessage.MessageType.INFO,
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)) {
610  throw new IngestModule.IngestModuleException(
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;
635  if (PlatformUtil.isWindowsOS()) {
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 }
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
synchronized Path ipToHostName(Path inputPath)
static synchronized IngestServices getInstance()

Copyright © 2012-2022 Basis Technology. Generated on: Tue Feb 6 2024
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.