19 package org.sleuthkit.autopsy.modules.photoreccarver;
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.Date;
33 import java.util.HashMap;
34 import java.util.List;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.atomic.AtomicLong;
38 import java.util.logging.Level;
39 import org.openide.modules.InstalledFileLocator;
40 import org.openide.util.NbBundle;
68 "PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing",
69 "PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See 'Shared Drive Authentication' in Autopsy help.",
70 "# {0} - output directory name",
"cannotCreateOutputDir.message=Unable to create output directory: {0}.",
71 "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.",
72 "unsupportedOS.message=PhotoRec module is supported on Windows platforms only.",
73 "missingExecutable.message=Unable to locate PhotoRec executable.",
74 "cannotRunExecutable.message=Unable to execute PhotoRec.",
75 "PhotoRecIngestModule.nonHostnameUNCPathUsed=PhotoRec cannot operate with a UNC path containing IP addresses."
79 private static final String PHOTOREC_DIRECTORY =
"photorec_exec";
80 private static final String PHOTOREC_EXECUTABLE =
"photorec_win.exe";
81 private static final String PHOTOREC_RESULTS_BASE =
"results";
82 private static final String PHOTOREC_RESULTS_EXTENDED =
"results.1";
83 private static final String PHOTOREC_REPORT =
"report.xml";
84 private static final String LOG_FILE =
"run_log.txt";
85 private static final String TEMP_DIR_NAME =
"temp";
86 private static final String SEP = System.getProperty(
"line.separator");
87 private static final Logger logger =
Logger.
getLogger(PhotoRecCarverFileIngestModule.class.getName());
88 private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs =
new HashMap<>();
90 private static final Map<Long, WorkingPaths> pathsByJob =
new ConcurrentHashMap<>();
92 private Path rootOutputDirPath;
93 private File executableFile;
100 private AtomicLong totalItemsRecovered =
new AtomicLong(0);
101 private AtomicLong totalItemsWithErrors =
new AtomicLong(0);
102 private AtomicLong totalWritetime =
new AtomicLong(0);
103 private AtomicLong totalParsetime =
new AtomicLong(0);
106 private static synchronized IngestJobTotals getTotalsForIngestJobs(
long ingestJobId) {
108 if (totals == null) {
109 totals =
new PhotoRecCarverFileIngestModule.IngestJobTotals();
110 totalsForIngestJobs.put(ingestJobId, totals);
115 private static synchronized void initTotalsForIngestJob(
long ingestJobId) {
116 IngestJobTotals totals =
new PhotoRecCarverFileIngestModule.IngestJobTotals();
117 totalsForIngestJobs.put(ingestJobId, totals);
124 public void startUp(IngestJobContext context)
throws IngestModule.IngestModuleException {
125 this.context = context;
127 this.jobId = this.context.getJobId();
133 if (!this.context.processingUnallocatedSpace()) {
134 throw new IngestModule.IngestModuleException(Bundle.unallocatedSpaceProcessingSettingsError_message());
137 this.rootOutputDirPath = createModuleOutputDirectoryForCase();
139 Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
140 executableFile = locateExecutable(execName.toString());
142 if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(
this.jobId) == 1) {
145 DateFormat dateFormat =
new SimpleDateFormat(
"MM-dd-yyyy-HH-mm-ss-SSSS");
146 Date date =
new Date();
147 String folder = this.context.getDataSource().getId() +
"_" + dateFormat.format(date);
148 Path outputDirPath = Paths.get(this.rootOutputDirPath.toAbsolutePath().toString(), folder);
149 Files.createDirectories(outputDirPath);
152 Path tempDirPath = Paths.get(outputDirPath.toString(), PhotoRecCarverFileIngestModule.TEMP_DIR_NAME);
153 Files.createDirectory(tempDirPath);
156 PhotoRecCarverFileIngestModule.pathsByJob.put(this.jobId,
new WorkingPaths(outputDirPath, tempDirPath));
159 initTotalsForIngestJob(jobId);
160 }
catch (SecurityException | IOException | UnsupportedOperationException ex) {
161 throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
170 public IngestModule.ProcessResult process(AbstractFile file) {
172 if (file.getType() != TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
173 return IngestModule.ProcessResult.OK;
177 IngestJobTotals totals = getTotalsForIngestJobs(jobId);
179 Path tempFilePath = null;
182 if (null == this.executableFile) {
183 logger.log(Level.SEVERE,
"PhotoRec carver called after failed start up");
184 return IngestModule.ProcessResult.ERROR;
190 long freeDiskSpace = IngestServices.getInstance().getFreeDiskSpace();
191 if ((freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && ((file.getSize() * 1.2) > freeDiskSpace)) {
192 logger.log(Level.SEVERE,
"PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space.",
193 new Object[]{file.getName(), PhotoRecCarverIngestModuleFactory.getModuleName()});
194 MessageNotifyUtil.Notify.error(NbBundle.getMessage(
this.getClass(),
"PhotoRecIngestModule.UnableToCarve", file.getName()),
195 NbBundle.getMessage(
this.getClass(),
"PhotoRecIngestModule.NotEnoughDiskSpace"));
196 return IngestModule.ProcessResult.ERROR;
198 if (this.context.fileIngestIsCancelled() ==
true) {
200 logger.log(Level.INFO,
"PhotoRec cancelled by user");
201 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.cancelledByUser"));
202 return IngestModule.ProcessResult.OK;
206 long writestart = System.currentTimeMillis();
207 WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.jobId);
208 tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName());
209 ContentUtils.writeToFile(file, tempFilePath.toFile(), context::fileIngestIsCancelled);
211 if (this.context.fileIngestIsCancelled() ==
true) {
213 logger.log(Level.INFO,
"PhotoRec cancelled by user");
214 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.cancelledByUser"));
215 return IngestModule.ProcessResult.OK;
219 Path outputDirPath = Paths.get(paths.getOutputDirPath().toString(), file.getName());
220 Files.createDirectory(outputDirPath);
221 File log =
new File(Paths.get(outputDirPath.toString(), LOG_FILE).toString());
224 ProcessBuilder processAndSettings =
new ProcessBuilder(
225 "\"" + executableFile +
"\"",
227 "\"" + outputDirPath.toAbsolutePath() + File.separator + PHOTOREC_RESULTS_BASE +
"\"",
229 "\"" + tempFilePath.toFile() +
"\"",
233 processAndSettings.environment().put(
"__COMPAT_LAYER",
"RunAsInvoker");
234 processAndSettings.redirectErrorStream(
true);
235 processAndSettings.redirectOutput(Redirect.appendTo(log));
237 FileIngestModuleProcessTerminator terminator =
new FileIngestModuleProcessTerminator(this.context,
true);
238 int exitValue = ExecUtil.execute(processAndSettings, terminator);
240 if (this.context.fileIngestIsCancelled() ==
true) {
242 cleanup(outputDirPath, tempFilePath);
243 logger.log(Level.INFO,
"PhotoRec cancelled by user");
244 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.cancelledByUser"));
245 return IngestModule.ProcessResult.OK;
246 }
else if (terminator.getTerminationCode() == ProcTerminationCode.TIME_OUT) {
247 cleanup(outputDirPath, tempFilePath);
248 String msg = NbBundle.getMessage(this.getClass(),
"PhotoRecIngestModule.processTerminated") + file.getName();
249 MessageNotifyUtil.Notify.error(NbBundle.getMessage(
this.getClass(),
"PhotoRecIngestModule.moduleError"), msg);
250 logger.log(Level.SEVERE, msg);
251 return IngestModule.ProcessResult.ERROR;
252 }
else if (0 != exitValue) {
254 cleanup(outputDirPath, tempFilePath);
255 totals.totalItemsWithErrors.incrementAndGet();
256 logger.log(Level.SEVERE,
"PhotoRec carver returned error exit value = {0} when scanning {1}",
257 new Object[]{exitValue, file.getName()});
258 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.error.exitValue",
259 new Object[]{exitValue, file.getName()}));
260 return IngestModule.ProcessResult.ERROR;
264 java.io.File oldAuditFile =
new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_RESULTS_EXTENDED, PHOTOREC_REPORT).toString());
265 java.io.File newAuditFile =
new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_REPORT).toString());
266 oldAuditFile.renameTo(newAuditFile);
268 if (this.context.fileIngestIsCancelled() ==
true) {
270 logger.log(Level.INFO,
"PhotoRec cancelled by user");
271 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.cancelledByUser"));
272 return IngestModule.ProcessResult.OK;
274 Path pathToRemove = Paths.get(outputDirPath.toAbsolutePath().toString());
275 try (DirectoryStream<Path> stream = Files.newDirectoryStream(pathToRemove)) {
276 for (Path entry : stream) {
277 if (Files.isDirectory(entry)) {
278 FileUtil.deleteDir(
new File(entry.toString()));
282 long writedelta = (System.currentTimeMillis() - writestart);
283 totals.totalWritetime.addAndGet(writedelta);
286 long calcstart = System.currentTimeMillis();
287 PhotoRecCarverOutputParser parser =
new PhotoRecCarverOutputParser(outputDirPath);
288 if (this.context.fileIngestIsCancelled() ==
true) {
290 logger.log(Level.INFO,
"PhotoRec cancelled by user");
291 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.cancelledByUser"));
292 return IngestModule.ProcessResult.OK;
294 List<LayoutFile> carvedItems = parser.parse(newAuditFile, file, context);
295 long calcdelta = (System.currentTimeMillis() - calcstart);
296 totals.totalParsetime.addAndGet(calcdelta);
297 if (carvedItems != null && !carvedItems.isEmpty()) {
298 totals.totalItemsRecovered.addAndGet(carvedItems.size());
299 context.addFilesToJob(
new ArrayList<>(carvedItems));
302 }
catch (IOException ex) {
303 totals.totalItemsWithErrors.incrementAndGet();
304 logger.log(Level.SEVERE,
"Error processing " + file.getName() +
" with PhotoRec carver", ex);
305 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.error.msg", file.getName()));
306 return IngestModule.ProcessResult.ERROR;
308 if (null != tempFilePath && Files.exists(tempFilePath)) {
310 tempFilePath.toFile().delete();
313 return IngestModule.ProcessResult.OK;
317 private void cleanup(Path outputDirPath, Path tempFilePath) {
319 FileUtil.deleteDir(
new File(outputDirPath.toString()));
320 if (null != tempFilePath && Files.exists(tempFilePath)) {
321 tempFilePath.toFile().delete();
325 private static synchronized void postSummary(
long jobId) {
326 IngestJobTotals jobTotals = totalsForIngestJobs.remove(jobId);
328 StringBuilder detailsSb =
new StringBuilder();
330 detailsSb.append(
"<table border='0' cellpadding='4' width='280'>");
332 detailsSb.append(
"<tr><td>")
333 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.numberOfCarved"))
335 detailsSb.append(
"<td>").append(jobTotals.totalItemsRecovered.get()).append(
"</td></tr>");
337 detailsSb.append(
"<tr><td>")
338 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.numberOfErrors"))
340 detailsSb.append(
"<td>").append(jobTotals.totalItemsWithErrors.get()).append(
"</td></tr>");
342 detailsSb.append(
"<tr><td>")
343 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.totalWritetime"))
344 .append(
"</td><td>").append(jobTotals.totalWritetime.get()).append(
"</td></tr>\n");
345 detailsSb.append(
"<tr><td>")
346 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.totalParsetime"))
347 .append(
"</td><td>").append(jobTotals.totalParsetime.get()).append(
"</td></tr>\n");
348 detailsSb.append(
"</table>");
350 IngestServices.getInstance().postMessage(IngestMessage.createMessage(
351 IngestMessage.MessageType.INFO,
352 PhotoRecCarverIngestModuleFactory.getModuleName(),
353 NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
354 "PhotoRecIngestModule.complete.photoRecResults"),
355 detailsSb.toString()));
363 public void shutDown() {
364 if (this.context != null && refCounter.decrementAndGet(
this.jobId) == 0) {
368 WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.jobId);
369 FileUtil.deleteDir(
new File(paths.getTempDirPath().toString()));
371 }
catch (SecurityException ex) {
372 logger.log(Level.SEVERE,
"Error shutting down PhotoRec carver module", ex);
383 this.outputDirPath = outputDirPath;
384 this.tempDirPath = tempDirPath;
387 Path getOutputDirPath() {
388 return this.outputDirPath;
391 Path getTempDirPath() {
392 return this.tempDirPath;
404 synchronized Path createModuleOutputDirectoryForCase() throws IngestModule.IngestModuleException {
405 Path path = Paths.get(Case.getCurrentCase().getModuleDirectory(), PhotoRecCarverIngestModuleFactory.getModuleName());
407 Files.createDirectory(path);
408 if (UNCPathUtilities.isUNC(path)) {
412 throw new IngestModule.IngestModuleException(Bundle.PhotoRecIngestModule_nonHostnameUNCPathUsed());
414 if (
false == FileUtil.hasReadWriteAccess(path)) {
415 throw new IngestModule.IngestModuleException(
416 Bundle.PhotoRecIngestModule_PermissionsNotSufficient() + SEP + path.toString() + SEP
417 + Bundle.PhotoRecIngestModule_PermissionsNotSufficientSeeReference()
421 }
catch (FileAlreadyExistsException ex) {
423 }
catch (IOException | SecurityException | UnsupportedOperationException ex) {
424 throw new IngestModule.IngestModuleException(Bundle.cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
438 public static File locateExecutable(String executableToFindName)
throws IngestModule.IngestModuleException {
440 if (!PlatformUtil.isWindowsOS()) {
441 throw new IngestModule.IngestModuleException(Bundle.unsupportedOS_message());
444 File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PhotoRecCarverFileIngestModule.class.getPackage().getName(),
false);
445 if (null == exeFile) {
446 throw new IngestModule.IngestModuleException(Bundle.missingExecutable_message());
449 if (!exeFile.canExecute()) {
450 throw new IngestModule.IngestModuleException(Bundle.cannotRunExecutable_message());
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
synchronized static Logger getLogger(String name)
synchronized Path ipToHostName(Path inputPath)
static synchronized IngestServices getInstance()