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.lang.reflect.InvocationTargetException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.List;
34 import java.util.HashSet;
35 import java.util.concurrent.ExecutionException;
36 import java.util.logging.Level;
37 import javax.swing.AbstractAction;
38 import javax.swing.JFileChooser;
39 import static javax.swing.JFileChooser.SAVE_DIALOG;
40 import javax.swing.JOptionPane;
41 import javax.swing.SwingUtilities;
42 import javax.swing.SwingWorker;
43 import org.netbeans.api.progress.ProgressHandle;
44 import org.openide.util.Cancellable;
45 import org.openide.util.NbBundle;
67 final class ExtractUnallocAction
extends AbstractAction {
69 private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
70 private static final long serialVersionUID = 1L;
72 private static final Set<String> volumesInProgress =
new HashSet<>();
73 private static final Set<Long> imagesInProgress =
new HashSet<>();
74 private static String userDefinedExportPath;
76 private final Volume volume;
77 private final Image image;
79 private final JFileChooserFactory chooserFactory;
87 ExtractUnallocAction(String title, Volume volume) {
88 this(title, null, volume);
100 ExtractUnallocAction(String title, Image image) {
101 this(title, image, null);
104 ExtractUnallocAction(String title, Image image, Volume volume) {
107 this.volume = volume;
110 chooserFactory =
new JFileChooserFactory(CustomFileChooser.class);
119 @NbBundle.Messages({
"# {0} - fileName",
120 "ExtractUnallocAction.volumeInProgress=Already extracting unallocated space into {0} - will skip this volume",
121 "ExtractUnallocAction.volumeError=Error extracting unallocated space from volume",
122 "ExtractUnallocAction.noFiles=No unallocated files found on volume",
123 "ExtractUnallocAction.imageError=Error extracting unallocated space from image",
124 "ExtractUnallocAction.noOpenCase.errMsg=No open case available."})
126 public void actionPerformed(ActionEvent event) {
130 openCase = Case.getCurrentCaseThrows();
131 }
catch (NoCurrentCaseException ex) {
132 MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
136 JFileChooser fileChooser = chooserFactory.getChooser();
138 fileChooser.setCurrentDirectory(
new File(getExportDirectory(openCase)));
139 if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog((Component) event.getSource())) {
143 String destination = fileChooser.getSelectedFile().getPath();
144 updateExportDirectory(destination, openCase);
146 if (image != null && isImageInProgress(image.getId())) {
147 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
148 JOptionPane.showMessageDialog(
new Frame(),
"Unallocated Space is already being extracted on this Image. Please select a different Image.");
152 ExtractUnallocWorker worker =
new ExtractUnallocWorker(openCase, destination);
163 private String getExportDirectory(Case openCase) {
164 String caseExportPath = openCase.getExportDirectory();
166 if (userDefinedExportPath == null) {
167 return caseExportPath;
170 File file =
new File(userDefinedExportPath);
171 if (file.exists() ==
false || file.isDirectory() ==
false) {
172 return caseExportPath;
175 return userDefinedExportPath;
187 private void updateExportDirectory(String exportPath, Case openCase) {
188 if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
189 userDefinedExportPath = null;
191 userDefinedExportPath = exportPath;
202 private List<LayoutFile> getUnallocFiles(Content content) {
203 UnallocVisitor unallocVisitor =
new UnallocVisitor();
205 for (Content contentChild : content.getChildren()) {
206 if (contentChild instanceof AbstractContent) {
207 return contentChild.accept(unallocVisitor);
210 }
catch (TskCoreException tce) {
211 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce);
213 return Collections.emptyList();
216 synchronized static private void addVolumeInProgress(String volumeOutputFileName)
throws TskCoreException {
217 if (volumesInProgress.contains(volumeOutputFileName)) {
218 throw new TskCoreException(
"Already writing unallocated space to " + volumeOutputFileName);
220 volumesInProgress.add(volumeOutputFileName);
223 synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
224 volumesInProgress.remove(volumeOutputFileName);
227 synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
228 return volumesInProgress.contains(volumeOutputFileName);
231 synchronized static private void addImageInProgress(Long objId)
throws TskCoreException {
232 if (imagesInProgress.contains(objId)) {
233 throw new TskCoreException(
"Image " + objId +
" is in use");
235 imagesInProgress.add(objId);
238 synchronized static private void removeImageInProgress(Long objId) {
239 imagesInProgress.remove(objId);
242 synchronized static private boolean isImageInProgress(Long objId) {
243 return imagesInProgress.contains(objId);
266 if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
267 double megabytes = ((bytes / 1024.0) / 1024.0);
268 if (megabytes <= Integer.MAX_VALUE) {
269 return (
int) Math.ceil(megabytes);
279 progress = ProgressHandle.createHandle(
280 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.progress.extractUnalloc.title"),
new Cancellable() {
282 public boolean cancel() {
283 logger.log(Level.INFO,
"Canceling extraction of unallocated space");
285 if (progress != null) {
286 progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
287 "ExtractUnallocAction.progress.displayName.cancelling.text"));
292 int MAX_BYTES = 8192;
293 byte[] buf =
new byte[MAX_BYTES];
296 progress.start(totalSizeinMegs);
300 currentlyProcessing = outputFileData.getFile();
301 logger.log(Level.INFO,
"Writing Unalloc file to {0}", currentlyProcessing.getPath());
302 try (OutputStream outputStream =
new FileOutputStream(currentlyProcessing)) {
305 while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
306 LayoutFile layoutFile = outputFileData.getLayouts().get(i);
307 long offsetPerFile = 0L;
309 while (offsetPerFile != layoutFile.getSize() && !
canceled) {
310 if (++kbs % 128 == 0) {
312 progress.progress(NbBundle.getMessage(
this.getClass(),
313 "ExtractUnallocAction.processing.counter.msg",
316 bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
317 offsetPerFile += bytesRead;
318 outputStream.write(buf, 0, bytesRead);
320 bytes += layoutFile.getSize();
323 outputStream.flush();
327 outputFileData.getFile().delete();
328 logger.log(Level.INFO,
"Canceled extraction of {0} and deleted file", outputFileData.getFileName());
330 logger.log(Level.INFO,
"Finished writing unalloc file {0}", outputFileData.getFile().getPath());
335 }
catch (IOException ex) {
336 logger.log(Level.WARNING,
"Could not create Unalloc File; error writing file", ex);
338 }
catch (TskCoreException ex) {
339 logger.log(Level.WARNING,
"Could not create Unalloc File; error getting image info", ex);
348 removeImageInProgress(image.getId());
351 removeVolumeInProgress(u.getFileName());
356 if (!canceled && !outputFileDataList.isEmpty()) {
358 "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
359 NbBundle.getMessage(
this.getClass(),
360 "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
361 outputFileDataList.get(0).getFile().getParent()));
363 }
catch (InterruptedException | ExecutionException ex) {
365 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.done.errMsg.title"),
366 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.done.errMsg.msg", ex.getMessage()));
367 logger.log(Level.SEVERE,
"Failed to extract unallocated space", ex);
369 catch (java.util.concurrent.CancellationException ex) {
374 List<OutputFileData> filesToExtract =
new ArrayList<>();
376 if (volume != null) {
378 filesToExtract.add(outputFileData);
381 if (hasVolumeSystem(image)) {
382 for (Volume v : getVolumes(image)) {
384 filesToExtract.add(outputFileData);
388 filesToExtract.add(outputFileData);
392 if (filesToExtract.isEmpty() ==
false) {
394 List<OutputFileData> copyList =
new ArrayList<OutputFileData>() {
396 addAll(filesToExtract);
401 outputFileData.setPath(destination);
402 if (outputFileData.getLayouts() != null && !outputFileData.getLayouts().isEmpty() && (!isVolumeInProgress(outputFileData.getFileName()))) {
406 if (outputFileData.getFile().exists()) {
409 SwingUtilities.invokeAndWait(
new Runnable() {
412 dialogResult.set(JOptionPane.showConfirmDialog(
new Frame(), NbBundle.getMessage(
this.getClass(),
413 "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
414 outputFileData.getFileName())));
417 }
catch (InterruptedException | InvocationTargetException ex) {
418 logger.log(Level.SEVERE,
"An error occured launching confirmation dialog for extract unalloc actions", ex);
421 if (dialogResult.
value == JOptionPane.YES_OPTION) {
423 outputFileData.getFile().delete();
426 copyList.remove(outputFileData);
431 if (outputFileData.getLayouts() == null) {
432 logger.log(Level.SEVERE,
"Tried to get unallocated content but the list of unallocated files was null");
433 }
else if (outputFileData.getLayouts().isEmpty()) {
434 logger.log(Level.WARNING,
"No unallocated files found in volume");
435 copyList.remove(outputFileData);
437 logger.log(Level.WARNING,
"Tried to get unallocated content but the volume is locked");
438 copyList.remove(outputFileData);
443 if (!copyList.isEmpty()) {
451 private void setDataFileList(List<OutputFileData> outputFileDataList)
throws TskCoreException {
454 addImageInProgress(image.getId());
461 addVolumeInProgress(outputFileData.getFileName());
462 totalBytes += outputFileData.getSizeInBytes();
463 this.outputFileDataList.add(outputFileData);
464 }
catch (TskCoreException ex) {
465 logger.log(Level.WARNING,
"Already extracting data into {0}", outputFileData.getFileName());
470 if (this.outputFileDataList.isEmpty()) {
471 throw new TskCoreException(
"No unallocated files can be extracted");
474 totalSizeinMegs =
toMb(totalBytes);
485 private boolean hasVolumeSystem(Image img) {
487 for (Content c : img.getChildren()) {
488 if (c instanceof VolumeSystem) {
492 }
catch (TskCoreException ex) {
493 logger.log(Level.SEVERE,
"Unable to determine if image has a volume system, extraction may be incomplete", ex);
506 private List<Volume> getVolumes(Image img) {
507 List<Volume> volumes =
new ArrayList<>();
509 for (Content v : img.getChildren().get(0).getChildren()) {
510 if (v instanceof Volume) {
511 volumes.add((Volume) v);
514 }
catch (TskCoreException tce) {
515 logger.log(Level.WARNING,
"Could not get volume information from image. Extraction may be incomplete", tce);
524 private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
536 return new ArrayList<LayoutFile>() {
553 public List<LayoutFile>
visit(FileSystem fs) {
555 for (Content c : fs.getChildren()) {
556 if (c instanceof AbstractFile) {
557 if (((AbstractFile) c).isRoot()) {
558 return c.accept(
this);
562 }
catch (TskCoreException ex) {
563 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex);
565 return Collections.emptyList();
577 public List<LayoutFile>
visit(VirtualDirectory vd) {
579 List<LayoutFile> layoutFiles =
new ArrayList<>();
580 for (Content layout : vd.getChildren()) {
581 if (layout instanceof LayoutFile) {
582 layoutFiles.add((LayoutFile) layout);
586 }
catch (TskCoreException ex) {
587 logger.log(Level.WARNING,
"Could not get list of Layout Files, failed at visiting Layout Directory", ex);
589 return Collections.emptyList();
602 public List<LayoutFile>
visit(Directory dir) {
604 for (Content c : dir.getChildren()) {
606 if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
607 return c.accept(
this);
610 }
catch (TskCoreException ex) {
611 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex);
613 return Collections.emptyList();
618 return Collections.emptyList();
627 private class SortObjId implements Comparator<LayoutFile> {
630 public int compare(LayoutFile o1, LayoutFile o2) {
631 if (o1.getId() == o2.getId()) {
634 if (o1.getId() > o2.getId()) {
664 this.layoutFiles = getUnallocFiles(img);
665 Collections.sort(layoutFiles,
new SortObjId());
667 this.imageId = img.getId();
668 this.imageName = img.getName();
669 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + 0 +
".dat";
683 this.imageName = volume.getDataSource().getName();
684 this.imageId = volume.getDataSource().getId();
685 this.volumeId = volume.getId();
686 }
catch (TskCoreException tce) {
687 logger.log(Level.WARNING,
"Unable to properly create ExtractUnallocAction, extraction may be incomplete", tce);
691 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + volumeId +
".dat";
693 this.layoutFiles = getUnallocFiles(volume);
694 Collections.sort(layoutFiles,
new SortObjId());
700 return layoutFiles.size();
705 for (LayoutFile f : layoutFiles) {
711 long getSizeInBytes() {
723 String getImageName() {
727 List<LayoutFile> getLayouts() {
731 String getFileName() {
739 void setPath(String path) {
740 this.fileInstance =
new File(path + File.separator +
this.fileName);
747 private static final long serialVersionUID = 1L;
755 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
756 setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
757 setAcceptAllFileFilterUsed(
false);
762 File f = getSelectedFile();
763 if (!f.exists() && getDialogType() == SAVE_DIALOG || !f.canWrite()) {
764 JOptionPane.showMessageDialog(
this, NbBundle.getMessage(
this.getClass(),
765 "ExtractUnallocAction.msgDlg.folderDoesntExist.msg"));
768 super.approveSelection();
777 void set(
int value) {
static void info(String title, String message)
static void error(String title, String message)
String getExportDirectory()