19 package org.sleuthkit.autopsy.directorytree;
21 import java.awt.Component;
22 import java.awt.Frame;
23 import java.awt.event.ActionEvent;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.List;
33 import java.util.HashSet;
34 import java.util.concurrent.ExecutionException;
35 import java.util.logging.Level;
36 import javax.swing.AbstractAction;
37 import javax.swing.JFileChooser;
38 import javax.swing.JOptionPane;
39 import javax.swing.SwingWorker;
40 import org.netbeans.api.progress.ProgressHandle;
41 import org.openide.util.Cancellable;
42 import org.openide.util.NbBundle;
63 final class ExtractUnallocAction
extends AbstractAction {
65 private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
67 private final List<OutputFileData> filesToExtract =
new ArrayList<>();
68 private static final Set<String> volumesInProgress =
new HashSet<>();
69 private static final Set<Long> imagesInProgress =
new HashSet<>();
70 private static String userDefinedExportPath;
71 private long currentImage = 0L;
72 private final boolean isImage;
80 public ExtractUnallocAction(String title, Volume volume) {
84 OutputFileData outputFileData =
new OutputFileData(volume);
85 filesToExtract.add(outputFileData);
86 }
catch (NoCurrentCaseException ex) {
87 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
100 public ExtractUnallocAction(String title, Image image)
throws NoCurrentCaseException {
103 currentImage = image.getId();
104 if (hasVolumeSystem(image)) {
105 for (Volume v : getVolumes(image)) {
106 OutputFileData outputFileData =
new OutputFileData(v);
107 filesToExtract.add(outputFileData);
110 OutputFileData outputFileData =
new OutputFileData(image);
111 filesToExtract.add(outputFileData);
121 @NbBundle.Messages({
"# {0} - fileName",
122 "ExtractUnallocAction.volumeInProgress=Already extracting unallocated space into {0} - will skip this volume",
123 "ExtractUnallocAction.volumeError=Error extracting unallocated space from volume",
124 "ExtractUnallocAction.noFiles=No unallocated files found on volume",
125 "ExtractUnallocAction.imageError=Error extracting unallocated space from image",
126 "ExtractUnallocAction.noOpenCase.errMsg=No open case available."})
128 public void actionPerformed(ActionEvent event) {
129 if (filesToExtract != null && filesToExtract.isEmpty() ==
false) {
132 if (isImage && isImageInProgress(currentImage)) {
133 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
139 openCase = Case.getCurrentCaseThrows();
140 }
catch (NoCurrentCaseException ex) {
141 MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
144 List<OutputFileData> copyList =
new ArrayList<OutputFileData>() {
146 addAll(filesToExtract);
150 JFileChooser fileChooser =
new JFileChooser() {
152 public void approveSelection() {
153 File f = getSelectedFile();
154 if (!f.exists() && getDialogType() == SAVE_DIALOG || !f.canWrite()) {
155 JOptionPane.showMessageDialog(
this, NbBundle.getMessage(
this.getClass(),
156 "ExtractUnallocAction.msgDlg.folderDoesntExist.msg"));
159 super.approveSelection();
163 fileChooser.setCurrentDirectory(
new File(getExportDirectory(openCase)));
164 fileChooser.setDialogTitle(
165 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
166 fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
167 fileChooser.setAcceptAllFileFilterUsed(
false);
168 int returnValue = fileChooser.showSaveDialog((Component) event.getSource());
169 if (returnValue == JFileChooser.APPROVE_OPTION) {
170 String destination = fileChooser.getSelectedFile().getPath();
172 updateExportDirectory(destination, openCase);
174 for (OutputFileData outputFileData : filesToExtract) {
175 outputFileData.setPath(destination);
177 if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (!isVolumeInProgress(outputFileData.getFileName()))) {
181 if (outputFileData.fileInstance.exists()) {
182 int res = JOptionPane.showConfirmDialog(
new Frame(), NbBundle.getMessage(
this.getClass(),
183 "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
184 outputFileData.getFileName()));
185 if (res == JOptionPane.YES_OPTION) {
187 outputFileData.fileInstance.delete();
190 copyList.remove(outputFileData);
194 if (!isImage & !copyList.isEmpty()) {
196 ExtractUnallocWorker worker =
new ExtractUnallocWorker(outputFileData);
198 }
catch (TskCoreException ex) {
199 logger.log(Level.WARNING,
"Already extracting unallocated space into {0}", outputFileData.getFileName());
200 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.volumeInProgress", outputFileData.getFileName()));
205 if (outputFileData.layoutFiles == null) {
206 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.volumeError"));
207 logger.log(Level.SEVERE,
"Tried to get unallocated content but the list of unallocated files was null");
208 }
else if (outputFileData.layoutFiles.isEmpty()) {
209 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.noFiles"));
210 logger.log(Level.WARNING,
"No unallocated files found in volume");
211 copyList.remove(outputFileData);
213 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.volumeInProgress", outputFileData.getFileName()));
214 logger.log(Level.WARNING,
"Tried to get unallocated content but the volume is locked");
215 copyList.remove(outputFileData);
224 if (isImage && !copyList.isEmpty()) {
226 ExtractUnallocWorker worker =
new ExtractUnallocWorker(copyList);
228 }
catch (Exception ex) {
229 logger.log(Level.WARNING,
"Error creating ExtractUnallocWorker", ex);
230 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.imageError"));
244 private String getExportDirectory(Case openCase) {
245 String caseExportPath = openCase.getExportDirectory();
247 if (userDefinedExportPath == null) {
248 return caseExportPath;
251 File file =
new File(userDefinedExportPath);
252 if (file.exists() ==
false || file.isDirectory() ==
false) {
253 return caseExportPath;
256 return userDefinedExportPath;
268 private void updateExportDirectory(String exportPath, Case openCase) {
269 if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
270 userDefinedExportPath = null;
272 userDefinedExportPath = exportPath;
283 private List<LayoutFile> getUnallocFiles(Content content) {
284 UnallocVisitor unallocVisitor =
new UnallocVisitor();
286 for (Content contentChild : content.getChildren()) {
287 if (contentChild instanceof AbstractContent) {
288 return contentChild.accept(unallocVisitor);
291 }
catch (TskCoreException tce) {
292 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce);
294 return Collections.emptyList();
297 synchronized static private void addVolumeInProgress(String volumeOutputFileName)
throws TskCoreException {
298 if (volumesInProgress.contains(volumeOutputFileName)) {
299 throw new TskCoreException(
"Already writing unallocated space to " + volumeOutputFileName);
301 volumesInProgress.add(volumeOutputFileName);
304 synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
305 volumesInProgress.remove(volumeOutputFileName);
308 synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
309 return volumesInProgress.contains(volumeOutputFileName);
312 synchronized static private void addImageInProgress(Long objId)
throws TskCoreException {
313 if (imagesInProgress.contains(objId)) {
314 throw new TskCoreException(
"Image " + objId +
" is in use");
316 imagesInProgress.add(objId);
319 synchronized static private void removeImageInProgress(Long objId) {
320 imagesInProgress.remove(objId);
323 synchronized static private boolean isImageInProgress(Long objId) {
324 return imagesInProgress.contains(objId);
341 addVolumeInProgress(outputFileData.getFileName());
342 outputFileDataList.add(outputFileData);
343 totalBytes = outputFileData.getSizeInBytes();
344 totalSizeinMegs =
toMb(totalBytes);
348 addImageInProgress(currentImage);
354 addVolumeInProgress(outputFileData.getFileName());
355 totalBytes += outputFileData.getSizeInBytes();
356 this.outputFileDataList.add(outputFileData);
357 }
catch (TskCoreException ex) {
358 logger.log(Level.WARNING,
"Already extracting data into {0}", outputFileData.getFileName());
363 if (this.outputFileDataList.isEmpty()) {
364 throw new TskCoreException(
"No unallocated files can be extracted");
367 totalSizeinMegs =
toMb(totalBytes);
371 if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
372 double megabytes = ((bytes / 1024.0) / 1024.0);
373 if (megabytes <= Integer.MAX_VALUE) {
374 return (
int) Math.ceil(megabytes);
383 progress = ProgressHandle.createHandle(
384 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.progress.extractUnalloc.title"),
new Cancellable() {
386 public boolean cancel() {
387 logger.log(Level.INFO,
"Canceling extraction of unallocated space");
389 if (progress != null) {
390 progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
391 "ExtractUnallocAction.progress.displayName.cancelling.text"));
396 int MAX_BYTES = 8192;
397 byte[] buf =
new byte[MAX_BYTES];
400 progress.start(totalSizeinMegs);
404 currentlyProcessing = outputFileData.getFile();
405 logger.log(Level.INFO,
"Writing Unalloc file to {0}", currentlyProcessing.getPath());
406 OutputStream outputStream =
new FileOutputStream(currentlyProcessing);
409 while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
410 LayoutFile layoutFile = outputFileData.getLayouts().get(i);
411 long offsetPerFile = 0L;
413 while (offsetPerFile != layoutFile.getSize() && !
canceled) {
414 if (++kbs % 128 == 0) {
416 progress.progress(NbBundle.getMessage(
this.getClass(),
417 "ExtractUnallocAction.processing.counter.msg",
420 bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
421 offsetPerFile += bytesRead;
422 outputStream.write(buf, 0, bytesRead);
424 bytes += layoutFile.getSize();
427 outputStream.flush();
428 outputStream.close();
431 outputFileData.getFile().delete();
432 logger.log(Level.INFO,
"Canceled extraction of {0} and deleted file", outputFileData.getFileName());
434 logger.log(Level.INFO,
"Finished writing unalloc file {0}", outputFileData.getFile().getPath());
439 }
catch (IOException ex) {
440 logger.log(Level.WARNING,
"Could not create Unalloc File; error writing file", ex);
442 }
catch (TskCoreException ex) {
443 logger.log(Level.WARNING,
"Could not create Unalloc File; error getting image info", ex);
452 removeImageInProgress(currentImage);
455 removeVolumeInProgress(u.getFileName());
460 if (!canceled && !outputFileDataList.isEmpty()) {
462 "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
463 NbBundle.getMessage(
this.getClass(),
464 "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
465 outputFileDataList.get(0).getFile().getParent()));
467 }
catch (InterruptedException | ExecutionException ex) {
469 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.done.errMsg.title"),
470 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.done.errMsg.msg", ex.getMessage()));
472 catch (java.util.concurrent.CancellationException ex) {
484 private boolean hasVolumeSystem(Image img) {
486 for (Content c : img.getChildren()) {
487 if (c instanceof VolumeSystem) {
491 }
catch (TskCoreException ex) {
492 logger.log(Level.SEVERE,
"Unable to determine if image has a volume system, extraction may be incomplete", ex);
505 private List<Volume> getVolumes(Image img) {
506 List<Volume> volumes =
new ArrayList<>();
508 for (Content v : img.getChildren().get(0).getChildren()) {
509 if (v instanceof Volume) {
510 volumes.add((Volume) v);
513 }
catch (TskCoreException tce) {
514 logger.log(Level.WARNING,
"Could not get volume information from image. Extraction may be incomplete", tce);
523 private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
535 return new ArrayList<LayoutFile>() {
552 public List<LayoutFile>
visit(FileSystem fs) {
554 for (Content c : fs.getChildren()) {
555 if (c instanceof AbstractFile) {
556 if (((AbstractFile) c).isRoot()) {
557 return c.accept(
this);
561 }
catch (TskCoreException ex) {
562 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex);
564 return Collections.emptyList();
576 public List<LayoutFile>
visit(VirtualDirectory vd) {
578 List<LayoutFile> layoutFiles =
new ArrayList<>();
579 for (Content layout : vd.getChildren()) {
580 if (layout instanceof LayoutFile) {
581 layoutFiles.add((LayoutFile) layout);
585 }
catch (TskCoreException ex) {
586 logger.log(Level.WARNING,
"Could not get list of Layout Files, failed at visiting Layout Directory", ex);
588 return Collections.emptyList();
601 public List<LayoutFile>
visit(Directory dir) {
603 for (Content c : dir.getChildren()) {
605 if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
606 return c.accept(
this);
609 }
catch (TskCoreException ex) {
610 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex);
612 return Collections.emptyList();
617 return Collections.emptyList();
626 private class SortObjId implements Comparator<LayoutFile> {
629 public int compare(LayoutFile o1, LayoutFile o2) {
630 if (o1.getId() == o2.getId()) {
633 if (o1.getId() > o2.getId()) {
663 this.layoutFiles = getUnallocFiles(img);
664 Collections.sort(layoutFiles,
new SortObjId());
666 this.imageId = img.getId();
667 this.imageName = img.getName();
668 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + 0 +
".dat";
682 this.imageName = volume.getDataSource().getName();
683 this.imageId = volume.getDataSource().getId();
684 this.volumeId = volume.getId();
685 }
catch (TskCoreException tce) {
686 logger.log(Level.WARNING,
"Unable to properly create ExtractUnallocAction, extraction may be incomplete", tce);
690 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + volumeId +
".dat";
691 this.fileInstance =
new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.
fileName);
692 this.layoutFiles = getUnallocFiles(volume);
693 Collections.sort(layoutFiles,
new SortObjId());
699 return layoutFiles.size();
704 for (LayoutFile f : layoutFiles) {
710 long getSizeInBytes() {
722 String getImageName() {
726 List<LayoutFile> getLayouts() {
730 String getFileName() {
738 void setPath(String path) {
739 this.fileInstance =
new File(path + File.separator +
this.fileName);
static void info(String title, String message)
static void error(String title, String message)
static Case getCurrentCaseThrows()
String getExportDirectory()