Autopsy  4.5.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-2017 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;
46 import org.sleuthkit.datamodel.AbstractContent;
47 import org.sleuthkit.datamodel.AbstractFile;
48 import org.sleuthkit.datamodel.Content;
49 import org.sleuthkit.datamodel.ContentVisitor;
50 import org.sleuthkit.datamodel.Directory;
51 import org.sleuthkit.datamodel.FileSystem;
52 import org.sleuthkit.datamodel.Image;
53 import org.sleuthkit.datamodel.LayoutFile;
54 import org.sleuthkit.datamodel.TskCoreException;
55 import org.sleuthkit.datamodel.VirtualDirectory;
56 import org.sleuthkit.datamodel.Volume;
57 import org.sleuthkit.datamodel.VolumeSystem;
58 
62 final class ExtractUnallocAction extends AbstractAction {
63  private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
64 
65  private final List<OutputFileData> filesToExtract = new ArrayList<>();
66  private static final Set<String> volumesInProgress = new HashSet<>();
67  private static final Set<Long> imagesInProgress = new HashSet<>();
68  private long currentImage = 0L;
69  private final boolean isImage;
70 
71  public ExtractUnallocAction(String title, Volume volume) {
72  super(title);
73  isImage = false;
74  OutputFileData outputFileData = new OutputFileData(volume);
75  filesToExtract.add(outputFileData);
76  }
77 
78  public ExtractUnallocAction(String title, Image image) {
79  super(title);
80  isImage = true;
81  currentImage = image.getId();
82  if (hasVolumeSystem(image)) {
83  for (Volume v : getVolumes(image)) {
84  OutputFileData outputFileData = new OutputFileData(v);
85  filesToExtract.add(outputFileData);
86  }
87  } else {
88  OutputFileData outputFileData = new OutputFileData(image);
89  filesToExtract.add(outputFileData);
90  }
91  }
92 
99  @NbBundle.Messages({"# {0} - fileName",
100  "ExtractUnallocAction.volumeInProgress=Already extracting unallocated space into {0} - will skip this volume",
101  "ExtractUnallocAction.volumeError=Error extracting unallocated space from volume",
102  "ExtractUnallocAction.noFiles=No unallocated files found on volume",
103  "ExtractUnallocAction.imageError=Error extracting unallocated space from image"})
104  @Override
105  public void actionPerformed(ActionEvent e) {
106  if (filesToExtract != null && filesToExtract.size() > 0) {
107  // This check doesn't absolutely guarantee that the image won't be in progress when we make the worker,
108  // but in general it will suffice.
109  if (isImage && isImageInProgress(currentImage)) {
110  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
111  //JOptionPane.showMessageDialog(new Frame(), "Unallocated Space is already being extracted on this Image. Please select a different Image.");
112  return;
113  }
114  List<OutputFileData> copyList = new ArrayList<OutputFileData>() {
115  {
116  addAll(filesToExtract);
117  }
118  };
119 
120  JFileChooser fileChooser = new JFileChooser() {
121  @Override
122  public void approveSelection() {
123  File f = getSelectedFile();
124  if (!f.exists() && getDialogType() == SAVE_DIALOG || !f.canWrite()) {
125  JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(),
126  "ExtractUnallocAction.msgDlg.folderDoesntExist.msg"));
127  return;
128  }
129  super.approveSelection();
130  }
131  };
132 
133  fileChooser.setCurrentDirectory(new File(Case.getCurrentCase().getExportDirectory()));
134  fileChooser.setDialogTitle(
135  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
136  fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
137  fileChooser.setAcceptAllFileFilterUsed(false);
138  int returnValue = fileChooser.showSaveDialog((Component) e.getSource());
139  if (returnValue == JFileChooser.APPROVE_OPTION) {
140  String destination = fileChooser.getSelectedFile().getPath();
141  for (OutputFileData outputFileData : filesToExtract) {
142  outputFileData.setPath(destination);
143 
144  if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (! isVolumeInProgress(outputFileData.getFileName()))) {
145  //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat
146 
147  // Check if there is already a file with this name
148  if (outputFileData.fileInstance.exists()) {
149  int res = JOptionPane.showConfirmDialog(new Frame(), NbBundle.getMessage(this.getClass(),
150  "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
151  outputFileData.getFileName()));
152  if (res == JOptionPane.YES_OPTION) {
153  // If the user wants to overwrite, delete the exising output file
154  outputFileData.fileInstance.delete();
155  } else {
156  // Otherwise remove it from the list of output files
157  copyList.remove(outputFileData);
158  }
159  }
160 
161  if (!isImage & !copyList.isEmpty()) {
162  try{
163  ExtractUnallocWorker worker = new ExtractUnallocWorker(outputFileData);
164  worker.execute();
165  } catch (Exception ex){
166  logger.log(Level.WARNING, "Already extracting unallocated space into " + outputFileData.getFileName());
167  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.volumeInProgress", outputFileData.getFileName()));
168  }
169  }
170  } else {
171  // The output file for this volume could not be created for one of the following reasons
172  if (outputFileData.layoutFiles == null){
173  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.volumeError"));
174  logger.log(Level.SEVERE, "Tried to get unallocated content but the list of unallocated files was null"); //NON-NLS
175  } else if (outputFileData.layoutFiles.isEmpty()){
176  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.noFiles"));
177  logger.log(Level.WARNING, "No unallocated files found in volume"); //NON-NLS
178  copyList.remove(outputFileData);
179  } else {
180  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.volumeInProgress", outputFileData.getFileName()));
181  logger.log(Level.WARNING, "Tried to get unallocated content but the volume is locked"); // NON_NLS
182  copyList.remove(outputFileData);
183  }
184  }
185  }
186 
187  // This needs refactoring. The idea seems to be that we'll take advantage of the loop above to
188  // check whether each output file exists but wait until this point to make a worker
189  // to extract everything (the worker in the above loop doesn't get created because isImage is true)
190  // It's also unclear to me why we need the two separate worker types.
191  if (isImage && !copyList.isEmpty()) {
192  try{
193  ExtractUnallocWorker worker = new ExtractUnallocWorker(copyList);
194  worker.execute();
195  } catch (Exception ex){
196  logger.log(Level.WARNING, "Error creating ExtractUnallocWorker", ex);
197  MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.imageError"));
198  }
199  }
200  }
201  }
202 
203  }
204 
212  private List<LayoutFile> getUnallocFiles(Content c) {
213  UnallocVisitor uv = new UnallocVisitor();
214  try {
215  for (Content contentChild : c.getChildren()) {
216  if (contentChild instanceof AbstractContent) {
217  return contentChild.accept(uv); //call on first non-artifact child added to database
218  }
219  }
220  } catch (TskCoreException tce) {
221  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); //NON-NLS
222  }
223  return Collections.emptyList();
224  }
225 
226  synchronized static private void addVolumeInProgress(String volumeOutputFileName) throws TskCoreException {
227  if(volumesInProgress.contains(volumeOutputFileName)){
228  throw new TskCoreException("Already writing unallocated space to " + volumeOutputFileName);
229  }
230  volumesInProgress.add(volumeOutputFileName);
231  }
232 
233  synchronized static private void removeVolumeInProgress(String volumeOutputFileName){
234  volumesInProgress.remove(volumeOutputFileName);
235  }
236 
237  synchronized static private boolean isVolumeInProgress(String volumeOutputFileName){
238  return volumesInProgress.contains(volumeOutputFileName);
239  }
240 
241  synchronized static private void addImageInProgress(Long id) throws TskCoreException {
242  if(imagesInProgress.contains(id)){
243  throw new TskCoreException("Image " + id + " is in use");
244  }
245  imagesInProgress.add(id);
246  }
247 
248  synchronized static private void removeImageInProgress(Long id){
249  imagesInProgress.remove(id);
250  }
251 
252  synchronized static private boolean isImageInProgress(Long id){
253  return imagesInProgress.contains(id);
254  }
255 
256 
260  private class ExtractUnallocWorker extends SwingWorker<Integer, Integer> {
261 
262  private ProgressHandle progress;
263  private boolean canceled = false;
264  private List<OutputFileData> outputFileDataList = new ArrayList<>();
265  private File currentlyProcessing;
266  private int totalSizeinMegs;
267  long totalBytes = 0;
268 
269  ExtractUnallocWorker(OutputFileData outputFileData) throws TskCoreException {
270  //Getting the total megs this worker is going to be doing
271  addVolumeInProgress(outputFileData.getFileName());
272  outputFileDataList.add(outputFileData);
273  totalBytes = outputFileData.getSizeInBytes();
274  totalSizeinMegs = toMb(totalBytes);
275  }
276 
277  ExtractUnallocWorker(List<OutputFileData> outputFileDataList) throws TskCoreException {
278  addImageInProgress(currentImage);
279 
280  //Getting the total megs this worker is going to be doing
281  for (OutputFileData outputFileData : outputFileDataList) {
282  try{
283  // If a volume is locked, skip it but continue trying to process any other requested volumes
284  addVolumeInProgress(outputFileData.getFileName());
285  totalBytes += outputFileData.getSizeInBytes();
286  this.outputFileDataList.add(outputFileData);
287  } catch (TskCoreException ex){
288  logger.log(Level.WARNING, "Already extracting data into " + outputFileData.getFileName());
289  }
290  }
291 
292  // If we don't have anything to output (because of locking), throw an exception
293  if(this.outputFileDataList.isEmpty()){
294  throw new TskCoreException("No unallocated files can be extracted");
295  }
296 
297  totalSizeinMegs = toMb(totalBytes);
298  }
299 
300  private int toMb(long bytes) {
301  if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
302  double Mb = ((bytes / 1024.0) / 1024.0);//Bytes -> Megabytes
303  if (Mb <= Integer.MAX_VALUE) {
304  return (int) Math.ceil(Mb);
305  }
306  }
307  return 0;
308  }
309 
310  @Override
311  protected Integer doInBackground() {
312  try {
313  progress = ProgressHandle.createHandle(
314  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.progress.extractUnalloc.title"), new Cancellable() {
315  @Override
316  public boolean cancel() {
317  logger.log(Level.INFO, "Canceling extraction of unallocated space"); //NON-NLS
318  canceled = true;
319  if (progress != null) {
320  progress.setDisplayName(NbBundle.getMessage(this.getClass(),
321  "ExtractUnallocAction.progress.displayName.cancelling.text"));
322  }
323  return true;
324  }
325  });
326  int MAX_BYTES = 8192;
327  byte[] buf = new byte[MAX_BYTES]; //read 8kb at a time
328 
329  //Begin the actual File IO
330  progress.start(totalSizeinMegs);
331  int kbs = 0; //Each completion of the while loop adds one to kbs. 16kb * 64 = 1mb.
332  int mbs = 0; //Increments every 128th tick of kbs
333  for (OutputFileData outputFileData : this.outputFileDataList) {
334  currentlyProcessing = outputFileData.getFile();
335  logger.log(Level.INFO, "Writing Unalloc file to " + currentlyProcessing.getPath()); //NON-NLS
336  OutputStream outputStream = new FileOutputStream(currentlyProcessing);
337  long bytes = 0;
338  int i = 0;
339  while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
340  LayoutFile layoutFile = outputFileData.getLayouts().get(i);
341  long offsetPerFile = 0L;
342  int bytesRead;
343  while (offsetPerFile != layoutFile.getSize() && !canceled) {
344  if (++kbs % 128 == 0) {
345  mbs++;
346  progress.progress(NbBundle.getMessage(this.getClass(),
347  "ExtractUnallocAction.processing.counter.msg",
348  mbs, totalSizeinMegs), mbs - 1);
349  }
350  bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
351  offsetPerFile += bytesRead;
352  outputStream.write(buf, 0, bytesRead);
353  }
354  bytes += layoutFile.getSize();
355  i++;
356  }
357  outputStream.flush();
358  outputStream.close();
359 
360  if (canceled) {
361  outputFileData.getFile().delete();
362  logger.log(Level.INFO, "Canceled extraction of " + outputFileData.getFileName() + " and deleted file"); //NON-NLS
363  } else {
364  logger.log(Level.INFO, "Finished writing unalloc file " + outputFileData.getFile().getPath()); //NON-NLS
365  }
366  }
367  progress.finish();
368 
369  } catch (IOException ex) {
370  logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ex); //NON-NLS
371  return -1;
372  } catch (TskCoreException ex) {
373  logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", ex); //NON-NLS
374  return -1;
375  }
376  return 1;
377  }
378 
379  @Override
380  protected void done() {
381  if (isImage) {
382  removeImageInProgress(currentImage);
383  }
384  for (OutputFileData u : outputFileDataList) {
385  removeVolumeInProgress(u.getFileName());
386  }
387 
388  try {
389  get();
390  if (!canceled && !outputFileDataList.isEmpty()) {
391  MessageNotifyUtil.Notify.info(NbBundle.getMessage(this.getClass(),
392  "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
393  NbBundle.getMessage(this.getClass(),
394  "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
395  outputFileDataList.get(0).getFile().getParent()));
396  }
397  } catch (InterruptedException | ExecutionException ex) {
399  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.done.errMsg.title"),
400  NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.done.errMsg.msg", ex.getMessage()));
401  } // catch and ignore if we were cancelled
402  catch (java.util.concurrent.CancellationException ex) {
403  }
404  }
405  }
406 
414  private boolean hasVolumeSystem(Image img) {
415  try {
416  for (Content c : img.getChildren()) {
417  if (c instanceof VolumeSystem) {
418  return true;
419  }
420  }
421  } catch (TskCoreException ex) {
422  logger.log(Level.SEVERE, "Unable to determine if image has a volume system, extraction may be incomplete", ex); //NON-NLS
423  }
424  return false;
425  }
426 
435  private List<Volume> getVolumes(Image img) {
436  List<Volume> volumes = new ArrayList<>();
437  try {
438  for (Content v : img.getChildren().get(0).getChildren()) {
439  if (v instanceof Volume) {
440  volumes.add((Volume) v);
441  }
442  }
443  } catch (TskCoreException tce) {
444  logger.log(Level.WARNING, "Could not get volume information from image. Extraction may be incomplete", tce); //NON-NLS
445  }
446  return volumes;
447  }
448 
453  private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
454 
463  @Override
464  public List<LayoutFile> visit(final org.sleuthkit.datamodel.LayoutFile lf) {
465  return new ArrayList<LayoutFile>() {
466  {
467  add(lf);
468  }
469  };
470  }
471 
481  @Override
482  public List<LayoutFile> visit(FileSystem fs) {
483  try {
484  for (Content c : fs.getChildren()) {
485  if (c instanceof AbstractFile) {
486  if (((AbstractFile) c).isRoot()) {
487  return c.accept(this);
488  }
489  }
490  }
491  } catch (TskCoreException ex) {
492  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex); //NON-NLS
493  }
494  return Collections.emptyList();
495  }
496 
505  @Override
506  public List<LayoutFile> visit(VirtualDirectory vd) {
507  try {
508  List<LayoutFile> layoutFiles = new ArrayList<>();
509  for (Content layout : vd.getChildren()) {
510  if (layout instanceof LayoutFile) {
511  layoutFiles.add((LayoutFile) layout);
512  }
513  }
514  return layoutFiles;
515  } catch (TskCoreException ex) {
516  logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory", ex); //NON-NLS
517  }
518  return Collections.emptyList();
519  }
520 
530  @Override
531  public List<LayoutFile> visit(Directory dir) {
532  try {
533  for (Content c : dir.getChildren()) {
534  // Only the $Unalloc dir will contain unallocated files
535  if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
536  return c.accept(this);
537  }
538  }
539  } catch (TskCoreException ex) {
540  logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex); //NON-NLS
541  }
542  return Collections.emptyList();
543  }
544 
545  @Override
546  protected List<LayoutFile> defaultVisit(Content cntnt) {
547  return Collections.emptyList();
548  }
549  }
550 
556  private class SortObjId implements Comparator<LayoutFile> {
557 
558  @Override
559  public int compare(LayoutFile o1, LayoutFile o2) {
560  if (o1.getId() == o2.getId()) {
561  return 0;
562  }
563  if (o1.getId() > o2.getId()) {
564  return 1;
565  } else {
566  return -1;
567  }
568  }
569  }
570 
575  private class OutputFileData {
576 
577  private List<LayoutFile> layoutFiles;
578  private final long sizeInBytes;
579  private long volumeId;
580  private long imageId;
581  private String imageName;
582  private final String fileName;
583  private File fileInstance;
584 
590  OutputFileData(Image img) {
591  this.layoutFiles = getUnallocFiles(img);
592  Collections.sort(layoutFiles, new SortObjId());
593  this.volumeId = 0;
594  this.imageId = img.getId();
595  this.imageName = img.getName();
596  this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + 0 + ".dat"; //NON-NLS
597  this.fileInstance = new File(Case.getCurrentCase().getExportDirectory() + File.separator + this.fileName);
598  this.sizeInBytes = calcSizeInBytes();
599  }
600 
606  OutputFileData(Volume volume) {
607  try {
608  this.imageName = volume.getDataSource().getName();
609  this.imageId = volume.getDataSource().getId();
610  this.volumeId = volume.getId();
611  } catch (TskCoreException tce) {
612  logger.log(Level.WARNING, "Unable to properly create ExtractUnallocAction, extraction may be incomplete", tce); //NON-NLS
613  this.imageName = "";
614  this.imageId = 0;
615  }
616  this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + volumeId + ".dat"; //NON-NLS
617  this.fileInstance = new File(Case.getCurrentCase().getExportDirectory() + File.separator + this.fileName);
618  this.layoutFiles = getUnallocFiles(volume);
619  Collections.sort(layoutFiles, new SortObjId());
620  this.sizeInBytes = calcSizeInBytes();
621  }
622 
623  //Getters
624  int size() {
625  return layoutFiles.size();
626  }
627 
628  private long calcSizeInBytes() {
629  long size = 0L;
630  for (LayoutFile f : layoutFiles) {
631  size += f.getSize();
632  }
633  return size;
634  }
635 
636  long getSizeInBytes() {
637  return this.sizeInBytes;
638  }
639 
640  long getVolumeId() {
641  return this.volumeId;
642  }
643 
644  long getImageId() {
645  return this.imageId;
646  }
647 
648  String getImageName() {
649  return this.imageName;
650  }
651 
652  List<LayoutFile> getLayouts() {
653  return this.layoutFiles;
654  }
655 
656  String getFileName() {
657  return this.fileName;
658  }
659 
660  File getFile() {
661  return this.fileInstance;
662  }
663 
664  void setPath(String path) {
665  this.fileInstance = new File(path + File.separator + this.fileName);
666  }
667  }
668 }
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-2016 Basis Technology. Generated on: Tue Feb 20 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.