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.List;
35 import java.util.HashMap;
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;
73 private static final String PHOTOREC_DIRECTORY =
"photorec_exec";
74 private static final String PHOTOREC_EXECUTABLE =
"photorec_win.exe";
75 private static final String PHOTOREC_RESULTS_BASE =
"results";
76 private static final String PHOTOREC_RESULTS_EXTENDED =
"results.1";
77 private static final String PHOTOREC_REPORT =
"report.xml";
78 private static final String LOG_FILE =
"run_log.txt";
79 private static final String TEMP_DIR_NAME =
"temp";
80 private static final String SEP = System.getProperty(
"line.separator");
81 private static final Logger logger =
Logger.
getLogger(PhotoRecCarverFileIngestModule.class.getName());
82 private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs =
new HashMap<>();
84 private static final Map<Long, WorkingPaths> pathsByJob =
new ConcurrentHashMap<>();
86 private Path rootOutputDirPath;
87 private File executableFile;
100 private static synchronized IngestJobTotals getTotalsForIngestJobs(
long ingestJobId) {
102 if (totals == null) {
103 totals =
new PhotoRecCarverFileIngestModule.IngestJobTotals();
104 totalsForIngestJobs.put(ingestJobId, totals);
109 private static synchronized void initTotalsForIngestJob(
long ingestJobId) {
110 IngestJobTotals totals =
new PhotoRecCarverFileIngestModule.IngestJobTotals();
111 totalsForIngestJobs.put(ingestJobId, totals);
118 public void startUp(IngestJobContext context)
throws IngestModule.IngestModuleException {
119 this.context = context;
121 this.jobId = this.context.getJobId();
127 if (!this.context.processingUnallocatedSpace()) {
128 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"unallocatedSpaceProcessingSettingsError.message"));
131 this.rootOutputDirPath = createModuleOutputDirectoryForCase();
133 Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
134 executableFile = locateExecutable(execName.toString());
136 if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(
this.jobId) == 1) {
139 DateFormat dateFormat =
new SimpleDateFormat(
"MM-dd-yyyy-HH-mm-ss-SSSS");
140 Date date =
new Date();
141 String folder = this.context.getDataSource().getId() +
"_" + dateFormat.format(date);
142 Path outputDirPath = Paths.get(this.rootOutputDirPath.toAbsolutePath().toString(), folder);
143 Files.createDirectories(outputDirPath);
146 Path tempDirPath = Paths.get(outputDirPath.toString(), PhotoRecCarverFileIngestModule.TEMP_DIR_NAME);
147 Files.createDirectory(tempDirPath);
150 PhotoRecCarverFileIngestModule.pathsByJob.put(this.jobId,
new WorkingPaths(outputDirPath, tempDirPath));
153 initTotalsForIngestJob(jobId);
154 }
catch (SecurityException | IOException | UnsupportedOperationException ex) {
155 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"cannotCreateOutputDir.message", ex.getLocalizedMessage()), ex);
164 public IngestModule.ProcessResult process(AbstractFile file) {
166 if (file.getType() != TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
167 return IngestModule.ProcessResult.OK;
171 IngestJobTotals totals = getTotalsForIngestJobs(jobId);
173 Path tempFilePath = null;
175 long id = getRootId(file);
178 return ProcessResult.ERROR;
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;
200 long writestart = System.currentTimeMillis();
201 WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.jobId);
202 tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName());
203 ContentUtils.writeToFile(file, tempFilePath.toFile());
206 Path outputDirPath = Paths.get(paths.getOutputDirPath().toString(), file.getName());
207 Files.createDirectory(outputDirPath);
208 File log =
new File(Paths.get(outputDirPath.toString(), LOG_FILE).toString());
211 ProcessBuilder processAndSettings =
new ProcessBuilder(
212 "\"" + executableFile +
"\"",
214 "\"" + outputDirPath.toAbsolutePath() + File.separator + PHOTOREC_RESULTS_BASE +
"\"",
216 "\"" + tempFilePath.toFile() +
"\"",
220 processAndSettings.environment().put(
"__COMPAT_LAYER",
"RunAsInvoker");
221 processAndSettings.redirectErrorStream(
true);
222 processAndSettings.redirectOutput(Redirect.appendTo(log));
224 FileIngestModuleProcessTerminator terminator =
new FileIngestModuleProcessTerminator(this.context,
true);
225 int exitValue = ExecUtil.execute(processAndSettings, terminator);
227 if (this.context.fileIngestIsCancelled() ==
true) {
229 cleanup(outputDirPath, tempFilePath);
230 logger.log(Level.INFO,
"PhotoRec cancelled by user");
231 MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.cancelledByUser"));
232 return IngestModule.ProcessResult.OK;
233 }
else if (terminator.getTerminationCode() == ProcTerminationCode.TIME_OUT) {
234 cleanup(outputDirPath, tempFilePath);
235 String msg = NbBundle.getMessage(this.getClass(),
"PhotoRecIngestModule.processTerminated") + file.getName();
236 MessageNotifyUtil.Notify.error(NbBundle.getMessage(
this.getClass(),
"PhotoRecIngestModule.moduleError"), msg);
237 logger.log(Level.SEVERE, msg);
238 return IngestModule.ProcessResult.ERROR;
239 }
else if (0 != exitValue) {
241 cleanup(outputDirPath, tempFilePath);
242 totals.totalItemsWithErrors.incrementAndGet();
243 logger.log(Level.SEVERE,
"PhotoRec carver returned error exit value = {0} when scanning {1}",
244 new Object[]{exitValue, file.getName()});
245 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.error.exitValue",
246 new Object[]{exitValue, file.getName()}));
247 return IngestModule.ProcessResult.ERROR;
251 java.io.File oldAuditFile =
new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_RESULTS_EXTENDED, PHOTOREC_REPORT).toString());
252 java.io.File newAuditFile =
new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_REPORT).toString());
253 oldAuditFile.renameTo(newAuditFile);
255 Path pathToRemove = Paths.get(outputDirPath.toAbsolutePath().toString());
256 try (DirectoryStream<Path> stream = Files.newDirectoryStream(pathToRemove)) {
257 for (Path entry : stream) {
258 if (Files.isDirectory(entry)) {
259 FileUtil.deleteDir(
new File(entry.toString()));
263 long writedelta = (System.currentTimeMillis() - writestart);
264 totals.totalWritetime.addAndGet(writedelta);
267 long calcstart = System.currentTimeMillis();
268 PhotoRecCarverOutputParser parser =
new PhotoRecCarverOutputParser(outputDirPath);
269 List<LayoutFile> carvedItems = parser.parse(newAuditFile,
id, file);
270 long calcdelta = (System.currentTimeMillis() - calcstart);
271 totals.totalParsetime.addAndGet(calcdelta);
272 if (carvedItems != null) {
273 totals.totalItemsRecovered.addAndGet(carvedItems.size());
274 context.addFilesToJob(
new ArrayList<>(carvedItems));
277 }
catch (IOException ex) {
278 totals.totalItemsWithErrors.incrementAndGet();
279 logger.log(Level.SEVERE,
"Error processing " + file.getName() +
" with PhotoRec carver", ex);
280 MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.error.msg", file.getName()));
281 return IngestModule.ProcessResult.ERROR;
283 if (null != tempFilePath && Files.exists(tempFilePath)) {
285 tempFilePath.toFile().delete();
288 return IngestModule.ProcessResult.OK;
292 private void cleanup(Path outputDirPath, Path tempFilePath) {
294 FileUtil.deleteDir(
new File(outputDirPath.toString()));
295 if (null != tempFilePath && Files.exists(tempFilePath)) {
296 tempFilePath.toFile().delete();
300 private static synchronized void postSummary(
long jobId) {
301 IngestJobTotals jobTotals = totalsForIngestJobs.remove(jobId);
303 StringBuilder detailsSb =
new StringBuilder();
305 detailsSb.append(
"<table border='0' cellpadding='4' width='280'>");
307 detailsSb.append(
"<tr><td>")
308 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.numberOfCarved"))
310 detailsSb.append(
"<td>").append(jobTotals.totalItemsRecovered.get()).append(
"</td></tr>");
312 detailsSb.append(
"<tr><td>")
313 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.numberOfErrors"))
315 detailsSb.append(
"<td>").append(jobTotals.totalItemsWithErrors.get()).append(
"</td></tr>");
317 detailsSb.append(
"<tr><td>")
318 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.totalWritetime"))
319 .append(
"</td><td>").append(jobTotals.totalWritetime.get()).append(
"</td></tr>\n");
320 detailsSb.append(
"<tr><td>")
321 .append(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.complete.totalParsetime"))
322 .append(
"</td><td>").append(jobTotals.totalParsetime.get()).append(
"</td></tr>\n");
323 detailsSb.append(
"</table>");
325 IngestServices.getInstance().postMessage(IngestMessage.createMessage(
326 IngestMessage.MessageType.INFO,
327 PhotoRecCarverIngestModuleFactory.getModuleName(),
328 NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
329 "PhotoRecIngestModule.complete.photoRecResults"),
330 detailsSb.toString()));
338 public void shutDown() {
339 if (this.context != null && refCounter.decrementAndGet(
this.jobId) == 0) {
343 WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.jobId);
344 FileUtil.deleteDir(
new File(paths.getTempDirPath().toString()));
346 }
catch (SecurityException ex) {
347 logger.log(Level.SEVERE,
"Error shutting down PhotoRec carver module", ex);
362 Path getOutputDirPath() {
366 Path getTempDirPath() {
379 synchronized Path createModuleOutputDirectoryForCase() throws IngestModule.IngestModuleException {
380 Path path = Paths.get(Case.getCurrentCase().getModuleDirectory(), PhotoRecCarverIngestModuleFactory.getModuleName());
382 Files.createDirectory(path);
383 if (UNCPathUtilities.isUNC(path)) {
387 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.nonHostnameUNCPathUsed"));
389 if (
false == FileUtil.hasReadWriteAccess(path)) {
390 throw new IngestModule.IngestModuleException(
391 NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.PermissionsNotSufficient")
392 + SEP + path.toString() + SEP
393 + NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"PhotoRecIngestModule.PermissionsNotSufficientSeeReference"));
396 }
catch (FileAlreadyExistsException ex) {
398 }
catch (IOException | SecurityException | UnsupportedOperationException ex) {
399 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"cannotCreateOutputDir.message", ex.getLocalizedMessage()), ex);
411 private static long getRootId(AbstractFile file) {
413 Content parent = null;
415 parent = file.getParent();
416 while (parent != null) {
417 if (parent instanceof Volume || parent instanceof Image) {
421 parent = parent.getParent();
423 }
catch (TskCoreException ex) {
424 logger.log(Level.SEVERE,
"PhotoRec carver exception while trying to get parent of AbstractFile.", ex);
438 public static File locateExecutable(String executableToFindName)
throws IngestModule.IngestModuleException {
440 if (!PlatformUtil.isWindowsOS()) {
441 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"unsupportedOS.message"));
444 File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PhotoRecCarverFileIngestModule.class.getPackage().getName(),
false);
445 if (null == exeFile) {
446 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"missingExecutable.message"));
449 if (!exeFile.canExecute()) {
450 throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class,
"cannotRunExecutable.message"));
AtomicLong totalItemsRecovered
AtomicLong totalParsetime
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
synchronized static Logger getLogger(String name)
AtomicLong totalWritetime
AtomicLong totalItemsWithErrors
synchronized Path ipToHostName(Path inputPath)
static synchronized IngestServices getInstance()