Autopsy  4.18.0
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.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.List;
32 import java.util.Set;
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;
47 import org.sleuthkit.datamodel.AbstractContent;
48 import org.sleuthkit.datamodel.AbstractFile;
49 import org.sleuthkit.datamodel.Content;
50 import org.sleuthkit.datamodel.ContentVisitor;
51 import org.sleuthkit.datamodel.Directory;
52 import org.sleuthkit.datamodel.FileSystem;
53 import org.sleuthkit.datamodel.Image;
54 import org.sleuthkit.datamodel.LayoutFile;
55 import org.sleuthkit.datamodel.TskCoreException;
56 import org.sleuthkit.datamodel.VirtualDirectory;
57 import org.sleuthkit.datamodel.Volume;
58 import org.sleuthkit.datamodel.VolumeSystem;
59 
63 final class ExtractUnallocAction extends AbstractAction {
64 
65  private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
66 
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;
73 
80  public ExtractUnallocAction(String title, Volume volume) {
81  super(title);
82  isImage = false;
83  try {
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);
88  setEnabled(false);
89  }
90 
91  }
92 
100  public ExtractUnallocAction(String title, Image image) throws NoCurrentCaseException {
101  super(title);
102  isImage = true;
103  currentImage = image.getId();
104  if (hasVolumeSystem(image)) {
105  for (Volume v : getVolumes(image)) {
106  OutputFileData outputFileData = new OutputFileData(v);
107  filesToExtract.add(outputFileData);
108  }
109  } else {
110  OutputFileData outputFileData = new OutputFileData(image);
111  filesToExtract.add(outputFileData);
112  }
113  }
114 
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."})
127  @Override
128  public void actionPerformed(ActionEvent event) {
129  if (filesToExtract != null && filesToExtract.isEmpty() == false) {
130  // This check doesn't absolutely guarantee that the image won't be in progress when we make the worker,
131  // but in general it will suffice.
132  if (isImage && isImageInProgress(currentImage)) {
133  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
134  //JOptionPane.showMessageDialog(new Frame(), "Unallocated Space is already being extracted on this Image. Please select a different Image.");
135  return;
136  }
137  Case openCase;
138  try {
139  openCase = Case.getCurrentCaseThrows();
140  } catch (NoCurrentCaseException ex) {
141  MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
142  return;
143  }
144  List<OutputFileData> copyList = new ArrayList<OutputFileData>() {
145  {
146  addAll(filesToExtract);
147  }
148  };
149 
150  JFileChooser fileChooser = new JFileChooser() {
151  @Override
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"));
157  return;
158  }
159  super.approveSelection();
160  }
161  };
162 
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();
171 
172  updateExportDirectory(destination, openCase);
173 
174  for (OutputFileData outputFileData : filesToExtract) {
175  outputFileData.setPath(destination);
176 
177  if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (!isVolumeInProgress(outputFileData.getFileName()))) {
178  //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat
179 
180  // Check if there is already a file with this name
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) {
186  // If the user wants to overwrite, delete the exising output file
187  outputFileData.fileInstance.delete();
188  } else {
189  // Otherwise remove it from the list of output files
190  copyList.remove(outputFileData);
191  }
192  }
193 
194  if (!isImage & !copyList.isEmpty()) {
195  try {
196  ExtractUnallocWorker worker = new ExtractUnallocWorker(outputFileData);
197  worker.execute();
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()));
201  }
202  }
203  } else {
204  // The output file for this volume could not be created for one of the following reasons
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"); //NON-NLS
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"); //NON-NLS
211  copyList.remove(outputFileData);
212  } else {
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"); // NON_NLS
215  copyList.remove(outputFileData);
216  }
217  }
218  }
219 
220  // This needs refactoring. The idea seems to be that we'll take advantage of the loop above to
221  // check whether each output file exists but wait until this point to make a worker
222  // to extract everything (the worker in the above loop doesn't get created because isImage is true)
223  // It's also unclear to me why we need the two separate worker types.
224  if (isImage && !copyList.isEmpty()) {
225  try {
226  ExtractUnallocWorker worker = new ExtractUnallocWorker(copyList);
227  worker.execute();
228  } catch (Exception ex) {
229  logger.log(Level.WARNING, "Error creating ExtractUnallocWorker", ex);
230  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.imageError"));
231  }
232  }
233  }
234  }
235  }
236 
244  private String getExportDirectory(Case openCase) {
245  String caseExportPath = openCase.getExportDirectory();
246 
247  if (userDefinedExportPath == null) {
248  return caseExportPath;
249  }
250 
251  File file = new File(userDefinedExportPath);
252  if (file.exists() == false || file.isDirectory() == false) {
253  return caseExportPath;
254  }
255 
256  return userDefinedExportPath;
257  }
258 
268  private void updateExportDirectory(String exportPath, Case openCase) {
269  if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
270  userDefinedExportPath = null;
271  } else {
272  userDefinedExportPath = exportPath;
273  }
274  }
275 
283  private List<LayoutFile> getUnallocFiles(Content content) {
284  UnallocVisitor unallocVisitor = new UnallocVisitor();
285  try {
286  for (Content contentChild : content.getChildren()) {
287  if (contentChild instanceof AbstractContent) {
288  return contentChild.accept(unallocVisitor); //call on first non-artifact child added to database
289  }
290  }
291  } catch (TskCoreException tce) {
292  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); //NON-NLS
293  }
294  return Collections.emptyList();
295  }
296 
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);
300  }
301  volumesInProgress.add(volumeOutputFileName);
302  }
303 
304  synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
305  volumesInProgress.remove(volumeOutputFileName);
306  }
307 
308  synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
309  return volumesInProgress.contains(volumeOutputFileName);
310  }
311 
312  synchronized static private void addImageInProgress(Long objId) throws TskCoreException {
313  if (imagesInProgress.contains(objId)) {
314  throw new TskCoreException("Image " + objId + " is in use");
315  }
316  imagesInProgress.add(objId);
317  }
318 
319  synchronized static private void removeImageInProgress(Long objId) {
320  imagesInProgress.remove(objId);
321  }
322 
323  synchronized static private boolean isImageInProgress(Long objId) {
324  return imagesInProgress.contains(objId);
325  }
326 
330  private class ExtractUnallocWorker extends SwingWorker<Integer, Integer> {
331 
332  private ProgressHandle progress;
333  private boolean canceled = false;
334  private final List<OutputFileData> outputFileDataList = new ArrayList<>();
335  private File currentlyProcessing;
336  private final int totalSizeinMegs;
337  long totalBytes = 0;
338 
339  ExtractUnallocWorker(OutputFileData outputFileData) throws TskCoreException {
340  //Getting the total megs this worker is going to be doing
341  addVolumeInProgress(outputFileData.getFileName());
342  outputFileDataList.add(outputFileData);
343  totalBytes = outputFileData.getSizeInBytes();
344  totalSizeinMegs = toMb(totalBytes);
345  }
346 
347  ExtractUnallocWorker(List<OutputFileData> outputFileDataList) throws TskCoreException {
348  addImageInProgress(currentImage);
349 
350  //Getting the total megs this worker is going to be doing
351  for (OutputFileData outputFileData : outputFileDataList) {
352  try {
353  // If a volume is locked, skip it but continue trying to process any other requested volumes
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());
359  }
360  }
361 
362  // If we don't have anything to output (because of locking), throw an exception
363  if (this.outputFileDataList.isEmpty()) {
364  throw new TskCoreException("No unallocated files can be extracted");
365  }
366 
367  totalSizeinMegs = toMb(totalBytes);
368  }
369 
370  private int toMb(long bytes) {
371  if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
372  double megabytes = ((bytes / 1024.0) / 1024.0);//Bytes -> Megabytes
373  if (megabytes <= Integer.MAX_VALUE) {
374  return (int) Math.ceil(megabytes);
375  }
376  }
377  return 0;
378  }
379 
380  @Override
381  protected Integer doInBackground() {
382  try {
383  progress = ProgressHandle.createHandle(
384  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.progress.extractUnalloc.title"), new Cancellable() {
385  @Override
386  public boolean cancel() {
387  logger.log(Level.INFO, "Canceling extraction of unallocated space"); //NON-NLS
388  canceled = true;
389  if (progress != null) {
390  progress.setDisplayName(NbBundle.getMessage(this.getClass(),
391  "ExtractUnallocAction.progress.displayName.cancelling.text"));
392  }
393  return true;
394  }
395  });
396  int MAX_BYTES = 8192;
397  byte[] buf = new byte[MAX_BYTES]; //read 8kb at a time
398 
399  //Begin the actual File IO
400  progress.start(totalSizeinMegs);
401  int kbs = 0; //Each completion of the while loop adds one to kbs. 16kb * 64 = 1mb.
402  int mbs = 0; //Increments every 128th tick of kbs
403  for (OutputFileData outputFileData : this.outputFileDataList) {
404  currentlyProcessing = outputFileData.getFile();
405  logger.log(Level.INFO, "Writing Unalloc file to {0}", currentlyProcessing.getPath()); //NON-NLS
406  OutputStream outputStream = new FileOutputStream(currentlyProcessing);
407  long bytes = 0;
408  int i = 0;
409  while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
410  LayoutFile layoutFile = outputFileData.getLayouts().get(i);
411  long offsetPerFile = 0L;
412  int bytesRead;
413  while (offsetPerFile != layoutFile.getSize() && !canceled) {
414  if (++kbs % 128 == 0) {
415  mbs++;
416  progress.progress(NbBundle.getMessage(this.getClass(),
417  "ExtractUnallocAction.processing.counter.msg",
418  mbs, totalSizeinMegs), mbs - 1);
419  }
420  bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
421  offsetPerFile += bytesRead;
422  outputStream.write(buf, 0, bytesRead);
423  }
424  bytes += layoutFile.getSize();
425  i++;
426  }
427  outputStream.flush();
428  outputStream.close();
429 
430  if (canceled) {
431  outputFileData.getFile().delete();
432  logger.log(Level.INFO, "Canceled extraction of {0} and deleted file", outputFileData.getFileName()); //NON-NLS
433  } else {
434  logger.log(Level.INFO, "Finished writing unalloc file {0}", outputFileData.getFile().getPath()); //NON-NLS
435  }
436  }
437  progress.finish();
438 
439  } catch (IOException ex) {
440  logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ex); //NON-NLS
441  return -1;
442  } catch (TskCoreException ex) {
443  logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", ex); //NON-NLS
444  return -1;
445  }
446  return 1;
447  }
448 
449  @Override
450  protected void done() {
451  if (isImage) {
452  removeImageInProgress(currentImage);
453  }
454  for (OutputFileData u : outputFileDataList) {
455  removeVolumeInProgress(u.getFileName());
456  }
457 
458  try {
459  get();
460  if (!canceled && !outputFileDataList.isEmpty()) {
461  MessageNotifyUtil.Notify.info(NbBundle.getMessage(this.getClass(),
462  "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
463  NbBundle.getMessage(this.getClass(),
464  "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
465  outputFileDataList.get(0).getFile().getParent()));
466  }
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()));
471  } // catch and ignore if we were cancelled
472  catch (java.util.concurrent.CancellationException ex) {
473  }
474  }
475  }
476 
484  private boolean hasVolumeSystem(Image img) {
485  try {
486  for (Content c : img.getChildren()) {
487  if (c instanceof VolumeSystem) {
488  return true;
489  }
490  }
491  } catch (TskCoreException ex) {
492  logger.log(Level.SEVERE, "Unable to determine if image has a volume system, extraction may be incomplete", ex); //NON-NLS
493  }
494  return false;
495  }
496 
505  private List<Volume> getVolumes(Image img) {
506  List<Volume> volumes = new ArrayList<>();
507  try {
508  for (Content v : img.getChildren().get(0).getChildren()) {
509  if (v instanceof Volume) {
510  volumes.add((Volume) v);
511  }
512  }
513  } catch (TskCoreException tce) {
514  logger.log(Level.WARNING, "Could not get volume information from image. Extraction may be incomplete", tce); //NON-NLS
515  }
516  return volumes;
517  }
518 
523  private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
524 
533  @Override
534  public List<LayoutFile> visit(final org.sleuthkit.datamodel.LayoutFile lf) {
535  return new ArrayList<LayoutFile>() {
536  {
537  add(lf);
538  }
539  };
540  }
541 
551  @Override
552  public List<LayoutFile> visit(FileSystem fs) {
553  try {
554  for (Content c : fs.getChildren()) {
555  if (c instanceof AbstractFile) {
556  if (((AbstractFile) c).isRoot()) {
557  return c.accept(this);
558  }
559  }
560  }
561  } catch (TskCoreException ex) {
562  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex); //NON-NLS
563  }
564  return Collections.emptyList();
565  }
566 
575  @Override
576  public List<LayoutFile> visit(VirtualDirectory vd) {
577  try {
578  List<LayoutFile> layoutFiles = new ArrayList<>();
579  for (Content layout : vd.getChildren()) {
580  if (layout instanceof LayoutFile) {
581  layoutFiles.add((LayoutFile) layout);
582  }
583  }
584  return layoutFiles;
585  } catch (TskCoreException ex) {
586  logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory", ex); //NON-NLS
587  }
588  return Collections.emptyList();
589  }
590 
600  @Override
601  public List<LayoutFile> visit(Directory dir) {
602  try {
603  for (Content c : dir.getChildren()) {
604  // Only the $Unalloc dir will contain unallocated files
605  if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
606  return c.accept(this);
607  }
608  }
609  } catch (TskCoreException ex) {
610  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex); //NON-NLS
611  }
612  return Collections.emptyList();
613  }
614 
615  @Override
616  protected List<LayoutFile> defaultVisit(Content cntnt) {
617  return Collections.emptyList();
618  }
619  }
620 
626  private class SortObjId implements Comparator<LayoutFile> {
627 
628  @Override
629  public int compare(LayoutFile o1, LayoutFile o2) {
630  if (o1.getId() == o2.getId()) {
631  return 0;
632  }
633  if (o1.getId() > o2.getId()) {
634  return 1;
635  } else {
636  return -1;
637  }
638  }
639  }
640 
645  private class OutputFileData {
646 
647  private final List<LayoutFile> layoutFiles;
648  private final long sizeInBytes;
649  private long volumeId;
650  private long imageId;
651  private String imageName;
652  private final String fileName;
653  private File fileInstance;
654 
662  OutputFileData(Image img) throws NoCurrentCaseException {
663  this.layoutFiles = getUnallocFiles(img);
664  Collections.sort(layoutFiles, new SortObjId());
665  this.volumeId = 0;
666  this.imageId = img.getId();
667  this.imageName = img.getName();
668  this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + 0 + ".dat"; //NON-NLS
669  this.fileInstance = new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.fileName);
670  this.sizeInBytes = calcSizeInBytes();
671  }
672 
680  OutputFileData(Volume volume) throws NoCurrentCaseException {
681  try {
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); //NON-NLS
687  this.imageName = "";
688  this.imageId = 0;
689  }
690  this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + volumeId + ".dat"; //NON-NLS
691  this.fileInstance = new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.fileName);
692  this.layoutFiles = getUnallocFiles(volume);
693  Collections.sort(layoutFiles, new SortObjId());
694  this.sizeInBytes = calcSizeInBytes();
695  }
696 
697  //Getters
698  int size() {
699  return layoutFiles.size();
700  }
701 
702  private long calcSizeInBytes() {
703  long size = 0L;
704  for (LayoutFile f : layoutFiles) {
705  size += f.getSize();
706  }
707  return size;
708  }
709 
710  long getSizeInBytes() {
711  return this.sizeInBytes;
712  }
713 
714  long getVolumeId() {
715  return this.volumeId;
716  }
717 
718  long getImageId() {
719  return this.imageId;
720  }
721 
722  String getImageName() {
723  return this.imageName;
724  }
725 
726  List<LayoutFile> getLayouts() {
727  return this.layoutFiles;
728  }
729 
730  String getFileName() {
731  return this.fileName;
732  }
733 
734  File getFile() {
735  return this.fileInstance;
736  }
737 
738  void setPath(String path) {
739  this.fileInstance = new File(path + File.separator + this.fileName);
740  }
741  }
742 }
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 Jul 8 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.