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