Autopsy  4.19.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractUnallocAction.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.directorytree;
20 
21 import java.awt.Component;
22 import java.awt.Frame;
23 import java.awt.event.ActionEvent;
24 import java.io.File;
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;
33 import java.util.Set;
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;
51 import org.sleuthkit.datamodel.AbstractContent;
52 import org.sleuthkit.datamodel.AbstractFile;
53 import org.sleuthkit.datamodel.Content;
54 import org.sleuthkit.datamodel.ContentVisitor;
55 import org.sleuthkit.datamodel.Directory;
56 import org.sleuthkit.datamodel.FileSystem;
57 import org.sleuthkit.datamodel.Image;
58 import org.sleuthkit.datamodel.LayoutFile;
59 import org.sleuthkit.datamodel.TskCoreException;
60 import org.sleuthkit.datamodel.VirtualDirectory;
61 import org.sleuthkit.datamodel.Volume;
62 import org.sleuthkit.datamodel.VolumeSystem;
63 
67 final class ExtractUnallocAction extends AbstractAction {
68 
69  private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
70  private static final long serialVersionUID = 1L;
71 
72  private static final Set<String> volumesInProgress = new HashSet<>();
73  private static final Set<Long> imagesInProgress = new HashSet<>();
74  private static String userDefinedExportPath;
75 
76  private final Volume volume;
77  private final Image image;
78 
79  private final JFileChooserFactory chooserFactory;
80 
87  ExtractUnallocAction(String title, Volume volume) {
88  this(title, null, volume);
89 
90  }
91 
100  ExtractUnallocAction(String title, Image image) {
101  this(title, image, null);
102  }
103 
104  ExtractUnallocAction(String title, Image image, Volume volume) {
105  super(title);
106 
107  this.volume = null;
108  this.image = image;
109 
110  chooserFactory = new JFileChooserFactory(CustomFileChooser.class);
111  }
112 
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."})
125  @Override
126  public void actionPerformed(ActionEvent event) {
127 
128  Case openCase;
129  try {
130  openCase = Case.getCurrentCaseThrows();
131  } catch (NoCurrentCaseException ex) {
132  MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
133  return;
134  }
135 
136  JFileChooser fileChooser = chooserFactory.getChooser();
137 
138  fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
139  if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog((Component) event.getSource())) {
140  return;
141  }
142 
143  String destination = fileChooser.getSelectedFile().getPath();
144  updateExportDirectory(destination, openCase);
145 
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.");
149  return;
150  }
151 
152  ExtractUnallocWorker worker = new ExtractUnallocWorker(openCase, destination);
153  worker.execute();
154  }
155 
163  private String getExportDirectory(Case openCase) {
164  String caseExportPath = openCase.getExportDirectory();
165 
166  if (userDefinedExportPath == null) {
167  return caseExportPath;
168  }
169 
170  File file = new File(userDefinedExportPath);
171  if (file.exists() == false || file.isDirectory() == false) {
172  return caseExportPath;
173  }
174 
175  return userDefinedExportPath;
176  }
177 
187  private void updateExportDirectory(String exportPath, Case openCase) {
188  if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
189  userDefinedExportPath = null;
190  } else {
191  userDefinedExportPath = exportPath;
192  }
193  }
194 
202  private List<LayoutFile> getUnallocFiles(Content content) {
203  UnallocVisitor unallocVisitor = new UnallocVisitor();
204  try {
205  for (Content contentChild : content.getChildren()) {
206  if (contentChild instanceof AbstractContent) {
207  return contentChild.accept(unallocVisitor); //call on first non-artifact child added to database
208  }
209  }
210  } catch (TskCoreException tce) {
211  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); //NON-NLS
212  }
213  return Collections.emptyList();
214  }
215 
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);
219  }
220  volumesInProgress.add(volumeOutputFileName);
221  }
222 
223  synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
224  volumesInProgress.remove(volumeOutputFileName);
225  }
226 
227  synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
228  return volumesInProgress.contains(volumeOutputFileName);
229  }
230 
231  synchronized static private void addImageInProgress(Long objId) throws TskCoreException {
232  if (imagesInProgress.contains(objId)) {
233  throw new TskCoreException("Image " + objId + " is in use");
234  }
235  imagesInProgress.add(objId);
236  }
237 
238  synchronized static private void removeImageInProgress(Long objId) {
239  imagesInProgress.remove(objId);
240  }
241 
242  synchronized static private boolean isImageInProgress(Long objId) {
243  return imagesInProgress.contains(objId);
244  }
245 
249  private class ExtractUnallocWorker extends SwingWorker<Integer, Integer> {
250 
251  private ProgressHandle progress;
252  private boolean canceled = false;
253  private final List<OutputFileData> outputFileDataList = new ArrayList<>();
254  private File currentlyProcessing;
255  private int totalSizeinMegs;
256  long totalBytes = 0;
257  private final String destination;
258  private Case openCase;
259 
260  ExtractUnallocWorker(Case openCase, String destination) {
261  this.destination = destination;
262  this.openCase = openCase;
263  }
264 
265  private int toMb(long bytes) {
266  if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
267  double megabytes = ((bytes / 1024.0) / 1024.0);//Bytes -> Megabytes
268  if (megabytes <= Integer.MAX_VALUE) {
269  return (int) Math.ceil(megabytes);
270  }
271  }
272  return 0;
273  }
274 
275  @Override
276  protected Integer doInBackground() {
277  try {
279  progress = ProgressHandle.createHandle(
280  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.progress.extractUnalloc.title"), new Cancellable() {
281  @Override
282  public boolean cancel() {
283  logger.log(Level.INFO, "Canceling extraction of unallocated space"); //NON-NLS
284  canceled = true;
285  if (progress != null) {
286  progress.setDisplayName(NbBundle.getMessage(this.getClass(),
287  "ExtractUnallocAction.progress.displayName.cancelling.text"));
288  }
289  return true;
290  }
291  });
292  int MAX_BYTES = 8192;
293  byte[] buf = new byte[MAX_BYTES]; //read 8kb at a time
294 
295  //Begin the actual File IO
296  progress.start(totalSizeinMegs);
297  int kbs = 0; //Each completion of the while loop adds one to kbs. 16kb * 64 = 1mb.
298  int mbs = 0; //Increments every 128th tick of kbs
299  for (OutputFileData outputFileData : this.outputFileDataList) {
300  currentlyProcessing = outputFileData.getFile();
301  logger.log(Level.INFO, "Writing Unalloc file to {0}", currentlyProcessing.getPath()); //NON-NLS
302  try (OutputStream outputStream = new FileOutputStream(currentlyProcessing)) {
303  long bytes = 0;
304  int i = 0;
305  while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
306  LayoutFile layoutFile = outputFileData.getLayouts().get(i);
307  long offsetPerFile = 0L;
308  int bytesRead;
309  while (offsetPerFile != layoutFile.getSize() && !canceled) {
310  if (++kbs % 128 == 0) {
311  mbs++;
312  progress.progress(NbBundle.getMessage(this.getClass(),
313  "ExtractUnallocAction.processing.counter.msg",
314  mbs, totalSizeinMegs), mbs - 1);
315  }
316  bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
317  offsetPerFile += bytesRead;
318  outputStream.write(buf, 0, bytesRead);
319  }
320  bytes += layoutFile.getSize();
321  i++;
322  }
323  outputStream.flush();
324  }
325 
326  if (canceled) {
327  outputFileData.getFile().delete();
328  logger.log(Level.INFO, "Canceled extraction of {0} and deleted file", outputFileData.getFileName()); //NON-NLS
329  } else {
330  logger.log(Level.INFO, "Finished writing unalloc file {0}", outputFileData.getFile().getPath()); //NON-NLS
331  }
332  }
333  progress.finish();
334 
335  } catch (IOException ex) {
336  logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ex); //NON-NLS
337  return -1;
338  } catch (TskCoreException ex) {
339  logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", ex); //NON-NLS
340  return -1;
341  }
342  return 1;
343  }
344 
345  @Override
346  protected void done() {
347  if (image != null) {
348  removeImageInProgress(image.getId());
349  }
350  for (OutputFileData u : outputFileDataList) {
351  removeVolumeInProgress(u.getFileName());
352  }
353 
354  try {
355  get();
356  if (!canceled && !outputFileDataList.isEmpty()) {
357  MessageNotifyUtil.Notify.info(NbBundle.getMessage(this.getClass(),
358  "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
359  NbBundle.getMessage(this.getClass(),
360  "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
361  outputFileDataList.get(0).getFile().getParent()));
362  }
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);
368  } // catch and ignore if we were cancelled
369  catch (java.util.concurrent.CancellationException ex) {
370  }
371  }
372 
373  private void initalizeFilesToExtract() throws TskCoreException {
374  List<OutputFileData> filesToExtract = new ArrayList<>();
375 
376  if (volume != null) {
377  OutputFileData outputFileData = new OutputFileData(volume, openCase);
378  filesToExtract.add(outputFileData);
379 
380  } else {
381  if (hasVolumeSystem(image)) {
382  for (Volume v : getVolumes(image)) {
383  OutputFileData outputFileData = new OutputFileData(v, openCase);
384  filesToExtract.add(outputFileData);
385  }
386  } else {
387  OutputFileData outputFileData = new OutputFileData(image, openCase);
388  filesToExtract.add(outputFileData);
389  }
390  }
391 
392  if (filesToExtract.isEmpty() == false) {
393 
394  List<OutputFileData> copyList = new ArrayList<OutputFileData>() {
395  {
396  addAll(filesToExtract);
397  }
398  };
399 
400  for (OutputFileData outputFileData : filesToExtract) {
401  outputFileData.setPath(destination);
402  if (outputFileData.getLayouts() != null && !outputFileData.getLayouts().isEmpty() && (!isVolumeInProgress(outputFileData.getFileName()))) {
403  //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat
404 
405  // Check if there is already a file with this name
406  if (outputFileData.getFile().exists()) {
407  final Result dialogResult = new Result();
408  try {
409  SwingUtilities.invokeAndWait(new Runnable() {
410  @Override
411  public void run() {
412  dialogResult.set(JOptionPane.showConfirmDialog(new Frame(), NbBundle.getMessage(this.getClass(),
413  "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
414  outputFileData.getFileName())));
415  }
416  });
417  } catch (InterruptedException | InvocationTargetException ex) {
418  logger.log(Level.SEVERE, "An error occured launching confirmation dialog for extract unalloc actions", ex);
419  }
420 
421  if (dialogResult.value == JOptionPane.YES_OPTION) {
422  // If the user wants to overwrite, delete the exising output file
423  outputFileData.getFile().delete();
424  } else {
425  // Otherwise remove it from the list of output files
426  copyList.remove(outputFileData);
427  }
428  }
429  } else {
430  // The output file for this volume could not be created for one of the following reasons
431  if (outputFileData.getLayouts() == null) {
432  logger.log(Level.SEVERE, "Tried to get unallocated content but the list of unallocated files was null"); //NON-NLS
433  } else if (outputFileData.getLayouts().isEmpty()) {
434  logger.log(Level.WARNING, "No unallocated files found in volume"); //NON-NLS
435  copyList.remove(outputFileData);
436  } else {
437  logger.log(Level.WARNING, "Tried to get unallocated content but the volume is locked"); // NON_NLS
438  copyList.remove(outputFileData);
439  }
440  }
441  }
442 
443  if (!copyList.isEmpty()) {
444 
445  setDataFileList(copyList);
446 
447  }
448  }
449  }
450 
451  private void setDataFileList(List<OutputFileData> outputFileDataList) throws TskCoreException {
452 
453  if (image != null) {
454  addImageInProgress(image.getId());
455  }
456 
457  //Getting the total megs this worker is going to be doing
458  for (OutputFileData outputFileData : outputFileDataList) {
459  try {
460  // If a volume is locked, skip it but continue trying to process any other requested volumes
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());
466  }
467  }
468 
469  // If we don't have anything to output (because of locking), throw an exception
470  if (this.outputFileDataList.isEmpty()) {
471  throw new TskCoreException("No unallocated files can be extracted");
472  }
473 
474  totalSizeinMegs = toMb(totalBytes);
475  }
476  }
477 
485  private boolean hasVolumeSystem(Image img) {
486  try {
487  for (Content c : img.getChildren()) {
488  if (c instanceof VolumeSystem) {
489  return true;
490  }
491  }
492  } catch (TskCoreException ex) {
493  logger.log(Level.SEVERE, "Unable to determine if image has a volume system, extraction may be incomplete", ex); //NON-NLS
494  }
495  return false;
496  }
497 
506  private List<Volume> getVolumes(Image img) {
507  List<Volume> volumes = new ArrayList<>();
508  try {
509  for (Content v : img.getChildren().get(0).getChildren()) {
510  if (v instanceof Volume) {
511  volumes.add((Volume) v);
512  }
513  }
514  } catch (TskCoreException tce) {
515  logger.log(Level.WARNING, "Could not get volume information from image. Extraction may be incomplete", tce); //NON-NLS
516  }
517  return volumes;
518  }
519 
524  private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
525 
534  @Override
535  public List<LayoutFile> visit(final org.sleuthkit.datamodel.LayoutFile lf) {
536  return new ArrayList<LayoutFile>() {
537  {
538  add(lf);
539  }
540  };
541  }
542 
552  @Override
553  public List<LayoutFile> visit(FileSystem fs) {
554  try {
555  for (Content c : fs.getChildren()) {
556  if (c instanceof AbstractFile) {
557  if (((AbstractFile) c).isRoot()) {
558  return c.accept(this);
559  }
560  }
561  }
562  } catch (TskCoreException ex) {
563  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex); //NON-NLS
564  }
565  return Collections.emptyList();
566  }
567 
576  @Override
577  public List<LayoutFile> visit(VirtualDirectory vd) {
578  try {
579  List<LayoutFile> layoutFiles = new ArrayList<>();
580  for (Content layout : vd.getChildren()) {
581  if (layout instanceof LayoutFile) {
582  layoutFiles.add((LayoutFile) layout);
583  }
584  }
585  return layoutFiles;
586  } catch (TskCoreException ex) {
587  logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory", ex); //NON-NLS
588  }
589  return Collections.emptyList();
590  }
591 
601  @Override
602  public List<LayoutFile> visit(Directory dir) {
603  try {
604  for (Content c : dir.getChildren()) {
605  // Only the $Unalloc dir will contain unallocated files
606  if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
607  return c.accept(this);
608  }
609  }
610  } catch (TskCoreException ex) {
611  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex); //NON-NLS
612  }
613  return Collections.emptyList();
614  }
615 
616  @Override
617  protected List<LayoutFile> defaultVisit(Content cntnt) {
618  return Collections.emptyList();
619  }
620  }
621 
627  private class SortObjId implements Comparator<LayoutFile> {
628 
629  @Override
630  public int compare(LayoutFile o1, LayoutFile o2) {
631  if (o1.getId() == o2.getId()) {
632  return 0;
633  }
634  if (o1.getId() > o2.getId()) {
635  return 1;
636  } else {
637  return -1;
638  }
639  }
640  }
641 
646  private class OutputFileData {
647 
648  private final List<LayoutFile> layoutFiles;
649  private final long sizeInBytes;
650  private long volumeId;
651  private long imageId;
652  private String imageName;
653  private final String fileName;
654  private File fileInstance;
655 
663  OutputFileData(Image img, Case openCase) {
664  this.layoutFiles = getUnallocFiles(img);
665  Collections.sort(layoutFiles, new SortObjId());
666  this.volumeId = 0;
667  this.imageId = img.getId();
668  this.imageName = img.getName();
669  this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + 0 + ".dat"; //NON-NLS
670  this.fileInstance = new File(openCase.getExportDirectory() + File.separator + this.fileName);
671  this.sizeInBytes = calcSizeInBytes();
672  }
673 
681  OutputFileData(Volume volume, Case openCase) {
682  try {
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); //NON-NLS
688  this.imageName = "";
689  this.imageId = 0;
690  }
691  this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + volumeId + ".dat"; //NON-NLS
692  this.fileInstance = new File(openCase.getExportDirectory() + File.separator + this.fileName);
693  this.layoutFiles = getUnallocFiles(volume);
694  Collections.sort(layoutFiles, new SortObjId());
695  this.sizeInBytes = calcSizeInBytes();
696  }
697 
698  //Getters
699  int size() {
700  return layoutFiles.size();
701  }
702 
703  private long calcSizeInBytes() {
704  long size = 0L;
705  for (LayoutFile f : layoutFiles) {
706  size += f.getSize();
707  }
708  return size;
709  }
710 
711  long getSizeInBytes() {
712  return this.sizeInBytes;
713  }
714 
715  long getVolumeId() {
716  return this.volumeId;
717  }
718 
719  long getImageId() {
720  return this.imageId;
721  }
722 
723  String getImageName() {
724  return this.imageName;
725  }
726 
727  List<LayoutFile> getLayouts() {
728  return this.layoutFiles;
729  }
730 
731  String getFileName() {
732  return this.fileName;
733  }
734 
735  File getFile() {
736  return this.fileInstance;
737  }
738 
739  void setPath(String path) {
740  this.fileInstance = new File(path + File.separator + this.fileName);
741  }
742  }
743 
744  // A Custome JFileChooser for this Action Class.
745  public static class CustomFileChooser extends JFileChooser {
746 
747  private static final long serialVersionUID = 1L;
748 
749  public CustomFileChooser() {
750  initalize();
751  }
752 
753  private void initalize() {
754  setDialogTitle(
755  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
756  setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
757  setAcceptAllFileFilterUsed(false);
758  }
759 
760  @Override
761  public void approveSelection() {
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"));
766  return;
767  }
768  super.approveSelection();
769  }
770  }
771 
772  // Small helper class for use with SwingUtilities involkAndWait to get
773  // the result from the launching of the JOptionPane.
774  private class Result {
775  private int value;
776 
777  void set(int value) {
778  this.value = value;
779  }
780 
781  int value() {
782  return value;
783  }
784  }
785 }
static void info(String title, String message)
List< LayoutFile > visit(final org.sleuthkit.datamodel.LayoutFile lf)
static void error(String title, String message)

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.