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.util.ArrayList;
 
   29 import java.util.Collections;
 
   30 import java.util.Comparator;
 
   31 import java.util.List;
 
   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;
 
   63 final class ExtractUnallocAction 
extends AbstractAction {
 
   65     private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
 
   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;
 
   80     public ExtractUnallocAction(String title, Volume volume) {
 
   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);
 
  100     public ExtractUnallocAction(String title, Image image) 
throws NoCurrentCaseException {
 
  103         currentImage = image.getId();
 
  104         if (hasVolumeSystem(image)) {
 
  105             for (Volume v : getVolumes(image)) {
 
  106                 OutputFileData outputFileData = 
new OutputFileData(v);
 
  107                 filesToExtract.add(outputFileData);
 
  110             OutputFileData outputFileData = 
new OutputFileData(image);
 
  111             filesToExtract.add(outputFileData);
 
  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."})
 
  128     public void actionPerformed(ActionEvent event) {
 
  129         if (filesToExtract != null && filesToExtract.isEmpty() == 
false) {
 
  132             if (isImage && isImageInProgress(currentImage)) {
 
  133                 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(), 
"ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
 
  139                 openCase = Case.getCurrentCaseThrows();
 
  140             } 
catch (NoCurrentCaseException ex) {
 
  141                 MessageNotifyUtil.Message.info(Bundle.ExtractAction_noOpenCase_errMsg());
 
  144             List<OutputFileData> copyList = 
new ArrayList<OutputFileData>() {
 
  146                     addAll(filesToExtract);
 
  150             JFileChooser fileChooser = 
new JFileChooser() {
 
  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"));
 
  159                     super.approveSelection();
 
  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();
 
  172                 updateExportDirectory(destination, openCase);
 
  174                 for (OutputFileData outputFileData : filesToExtract) {
 
  175                     outputFileData.setPath(destination);
 
  177                     if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (!isVolumeInProgress(outputFileData.getFileName()))) {
 
  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) {
 
  187                                 outputFileData.fileInstance.delete();
 
  190                                 copyList.remove(outputFileData);
 
  194                         if (!isImage & !copyList.isEmpty()) {
 
  196                                 ExtractUnallocWorker worker = 
new ExtractUnallocWorker(outputFileData);
 
  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()));
 
  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"); 
 
  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"); 
 
  211                             copyList.remove(outputFileData);
 
  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");  
 
  215                             copyList.remove(outputFileData);
 
  224                 if (isImage && !copyList.isEmpty()) {
 
  226                         ExtractUnallocWorker worker = 
new ExtractUnallocWorker(copyList);
 
  228                     } 
catch (Exception ex) {
 
  229                         logger.log(Level.WARNING, 
"Error creating ExtractUnallocWorker", ex);
 
  230                         MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(), 
"ExtractUnallocAction.imageError"));
 
  244     private String getExportDirectory(Case openCase) {
 
  245         String caseExportPath = openCase.getExportDirectory();
 
  247         if (userDefinedExportPath == null) {
 
  248             return caseExportPath;
 
  251         File file = 
new File(userDefinedExportPath);
 
  252         if (file.exists() == 
false || file.isDirectory() == 
false) {
 
  253             return caseExportPath;
 
  256         return userDefinedExportPath;
 
  268     private void updateExportDirectory(String exportPath, Case openCase) {
 
  269         if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
 
  270             userDefinedExportPath = null;
 
  272             userDefinedExportPath = exportPath;
 
  283     private List<LayoutFile> getUnallocFiles(Content content) {
 
  284         UnallocVisitor unallocVisitor = 
new UnallocVisitor();
 
  286             for (Content contentChild : content.getChildren()) {
 
  287                 if (contentChild instanceof AbstractContent) {
 
  288                     return contentChild.accept(unallocVisitor);  
 
  291         } 
catch (TskCoreException tce) {
 
  292             logger.log(Level.WARNING, 
"Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); 
 
  294         return Collections.emptyList();
 
  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);
 
  301         volumesInProgress.add(volumeOutputFileName);
 
  304     synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
 
  305         volumesInProgress.remove(volumeOutputFileName);
 
  308     synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
 
  309         return volumesInProgress.contains(volumeOutputFileName);
 
  312     synchronized static private void addImageInProgress(Long objId) 
throws TskCoreException {
 
  313         if (imagesInProgress.contains(objId)) {
 
  314             throw new TskCoreException(
"Image " + objId + 
" is in use");
 
  316         imagesInProgress.add(objId);
 
  319     synchronized static private void removeImageInProgress(Long objId) {
 
  320         imagesInProgress.remove(objId);
 
  323     synchronized static private boolean isImageInProgress(Long objId) {
 
  324         return imagesInProgress.contains(objId);
 
  341             addVolumeInProgress(outputFileData.getFileName());
 
  342             outputFileDataList.add(outputFileData);
 
  343             totalBytes = outputFileData.getSizeInBytes();
 
  344             totalSizeinMegs = 
toMb(totalBytes);
 
  348             addImageInProgress(currentImage);
 
  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());
 
  363             if (this.outputFileDataList.isEmpty()) {
 
  364                 throw new TskCoreException(
"No unallocated files can be extracted");
 
  367             totalSizeinMegs = 
toMb(totalBytes);
 
  371             if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
 
  372                 double megabytes = ((bytes / 1024.0) / 1024.0);
 
  373                 if (megabytes <= Integer.MAX_VALUE) {
 
  374                     return (
int) Math.ceil(megabytes);
 
  383                 progress = ProgressHandle.createHandle(
 
  384                         NbBundle.getMessage(
this.getClass(), 
"ExtractUnallocAction.progress.extractUnalloc.title"), 
new Cancellable() {
 
  386                     public boolean cancel() {
 
  387                         logger.log(Level.INFO, 
"Canceling extraction of unallocated space"); 
 
  389                         if (progress != null) {
 
  390                             progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
 
  391                                     "ExtractUnallocAction.progress.displayName.cancelling.text"));
 
  396                 int MAX_BYTES = 8192;
 
  397                 byte[] buf = 
new byte[MAX_BYTES];    
 
  400                 progress.start(totalSizeinMegs);
 
  404                     currentlyProcessing = outputFileData.getFile();
 
  405                     logger.log(Level.INFO, 
"Writing Unalloc file to {0}", currentlyProcessing.getPath()); 
 
  406                     OutputStream outputStream = 
new FileOutputStream(currentlyProcessing);
 
  409                     while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
 
  410                         LayoutFile layoutFile = outputFileData.getLayouts().get(i);
 
  411                         long offsetPerFile = 0L;
 
  413                         while (offsetPerFile != layoutFile.getSize() && !
canceled) {
 
  414                             if (++kbs % 128 == 0) {
 
  416                                 progress.progress(NbBundle.getMessage(
this.getClass(),
 
  417                                         "ExtractUnallocAction.processing.counter.msg",
 
  420                             bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
 
  421                             offsetPerFile += bytesRead;
 
  422                             outputStream.write(buf, 0, bytesRead);
 
  424                         bytes += layoutFile.getSize();
 
  427                     outputStream.flush();
 
  428                     outputStream.close();
 
  431                         outputFileData.getFile().delete();
 
  432                         logger.log(Level.INFO, 
"Canceled extraction of {0} and deleted file", outputFileData.getFileName()); 
 
  434                         logger.log(Level.INFO, 
"Finished writing unalloc file {0}", outputFileData.getFile().getPath()); 
 
  439             } 
catch (IOException ex) {
 
  440                 logger.log(Level.WARNING, 
"Could not create Unalloc File; error writing file", ex); 
 
  442             } 
catch (TskCoreException ex) {
 
  443                 logger.log(Level.WARNING, 
"Could not create Unalloc File; error getting image info", ex); 
 
  452                 removeImageInProgress(currentImage);
 
  455                 removeVolumeInProgress(u.getFileName());
 
  460                 if (!canceled && !outputFileDataList.isEmpty()) {
 
  462                             "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
 
  463                             NbBundle.getMessage(
this.getClass(),
 
  464                                     "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
 
  465                                     outputFileDataList.get(0).getFile().getParent()));
 
  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()));
 
  472             catch (java.util.concurrent.CancellationException ex) {
 
  484     private boolean hasVolumeSystem(Image img) {
 
  486             for (Content c : img.getChildren()) {
 
  487                 if (c instanceof VolumeSystem) {
 
  491         } 
catch (TskCoreException ex) {
 
  492             logger.log(Level.SEVERE, 
"Unable to determine if image has a volume system, extraction may be incomplete", ex); 
 
  505     private List<Volume> getVolumes(Image img) {
 
  506         List<Volume> volumes = 
new ArrayList<>();
 
  508             for (Content v : img.getChildren().get(0).getChildren()) {
 
  509                 if (v instanceof Volume) {
 
  510                     volumes.add((Volume) v);
 
  513         } 
catch (TskCoreException tce) {
 
  514             logger.log(Level.WARNING, 
"Could not get volume information from image. Extraction may be incomplete", tce); 
 
  523     private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
 
  535             return new ArrayList<LayoutFile>() {
 
  552         public List<LayoutFile> 
visit(FileSystem fs) {
 
  554                 for (Content c : fs.getChildren()) {
 
  555                     if (c instanceof AbstractFile) {
 
  556                         if (((AbstractFile) c).isRoot()) {
 
  557                             return c.accept(
this);
 
  561             } 
catch (TskCoreException ex) {
 
  562                 logger.log(Level.WARNING, 
"Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex); 
 
  564             return Collections.emptyList();
 
  576         public List<LayoutFile> 
visit(VirtualDirectory vd) {
 
  578                 List<LayoutFile> layoutFiles = 
new ArrayList<>();
 
  579                 for (Content layout : vd.getChildren()) {
 
  580                     if (layout instanceof LayoutFile) {
 
  581                         layoutFiles.add((LayoutFile) layout);
 
  585             } 
catch (TskCoreException ex) {
 
  586                 logger.log(Level.WARNING, 
"Could not get list of Layout Files, failed at visiting Layout Directory", ex); 
 
  588             return Collections.emptyList();
 
  601         public List<LayoutFile> 
visit(Directory dir) {
 
  603                 for (Content c : dir.getChildren()) {
 
  605                     if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
 
  606                         return c.accept(
this);
 
  609             } 
catch (TskCoreException ex) {
 
  610                 logger.log(Level.WARNING, 
"Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex); 
 
  612             return Collections.emptyList();
 
  617             return Collections.emptyList();
 
  626     private class SortObjId implements Comparator<LayoutFile> {
 
  629         public int compare(LayoutFile o1, LayoutFile o2) {
 
  630             if (o1.getId() == o2.getId()) {
 
  633             if (o1.getId() > o2.getId()) {
 
  663             this.layoutFiles = getUnallocFiles(img);
 
  664             Collections.sort(layoutFiles, 
new SortObjId());
 
  666             this.imageId = img.getId();
 
  667             this.imageName = img.getName();
 
  668             this.fileName = this.imageName + 
"-Unalloc-" + this.imageId + 
"-" + 0 + 
".dat"; 
 
  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); 
 
  690             this.fileName = this.imageName + 
"-Unalloc-" + this.imageId + 
"-" + volumeId + 
".dat"; 
 
  691             this.fileInstance = 
new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.
fileName);
 
  692             this.layoutFiles = getUnallocFiles(volume);
 
  693             Collections.sort(layoutFiles, 
new SortObjId());
 
  699             return layoutFiles.size();
 
  704             for (LayoutFile f : layoutFiles) {
 
  710         long getSizeInBytes() {
 
  722         String getImageName() {
 
  726         List<LayoutFile> getLayouts() {
 
  730         String getFileName() {
 
  738         void setPath(String path) {
 
  739             this.fileInstance = 
new File(path + File.separator + 
this.fileName);
 
static void info(String title, String message)
static void error(String title, String message)
static Case getCurrentCaseThrows()
String getExportDirectory()