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.lang.reflect.InvocationTargetException;
 
   29 import java.util.ArrayList;
 
   30 import java.util.Collections;
 
   31 import java.util.Comparator;
 
   32 import java.util.List;
 
   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;
 
   67 final class ExtractUnallocAction 
extends AbstractAction {
 
   69     private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
 
   70     private static final long serialVersionUID = 1L;
 
   72     private static final Set<String> volumesInProgress = 
new HashSet<>();
 
   73     private static final Set<Long> imagesInProgress = 
new HashSet<>();
 
   74     private static String userDefinedExportPath;
 
   76     private final Volume volume;
 
   77     private final Image image;
 
   79     private final JFileChooserFactory chooserFactory;
 
   87     ExtractUnallocAction(String title, Volume volume) {
 
   88         this(title, null, volume);
 
  100     ExtractUnallocAction(String title, Image image) {
 
  101         this(title, image, null);
 
  104     ExtractUnallocAction(String title, Image image, Volume volume) {
 
  110         chooserFactory = 
new JFileChooserFactory(CustomFileChooser.class);
 
  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."})
 
  126     public void actionPerformed(ActionEvent event) {
 
  130             openCase = Case.getCurrentCaseThrows();
 
  131         } 
catch (NoCurrentCaseException ex) {
 
  132             MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
 
  136         JFileChooser fileChooser = chooserFactory.getChooser();
 
  138         fileChooser.setCurrentDirectory(
new File(getExportDirectory(openCase)));
 
  139         if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog((Component) event.getSource())) {
 
  143         String destination = fileChooser.getSelectedFile().getPath();
 
  144         updateExportDirectory(destination, openCase);
 
  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.");
 
  152         ExtractUnallocWorker worker = 
new ExtractUnallocWorker(openCase, destination);
 
  163     private String getExportDirectory(Case openCase) {
 
  164         String caseExportPath = openCase.getExportDirectory();
 
  166         if (userDefinedExportPath == null) {
 
  167             return caseExportPath;
 
  170         File file = 
new File(userDefinedExportPath);
 
  171         if (file.exists() == 
false || file.isDirectory() == 
false) {
 
  172             return caseExportPath;
 
  175         return userDefinedExportPath;
 
  187     private void updateExportDirectory(String exportPath, Case openCase) {
 
  188         if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
 
  189             userDefinedExportPath = null;
 
  191             userDefinedExportPath = exportPath;
 
  202     private List<LayoutFile> getUnallocFiles(Content content) {
 
  203         UnallocVisitor unallocVisitor = 
new UnallocVisitor();
 
  205             for (Content contentChild : content.getChildren()) {
 
  206                 if (contentChild instanceof AbstractContent) {
 
  207                     return contentChild.accept(unallocVisitor);  
 
  210         } 
catch (TskCoreException tce) {
 
  211             logger.log(Level.WARNING, 
"Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); 
 
  213         return Collections.emptyList();
 
  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);
 
  220         volumesInProgress.add(volumeOutputFileName);
 
  223     synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
 
  224         volumesInProgress.remove(volumeOutputFileName);
 
  227     synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
 
  228         return volumesInProgress.contains(volumeOutputFileName);
 
  231     synchronized static private void addImageInProgress(Long objId) 
throws TskCoreException {
 
  232         if (imagesInProgress.contains(objId)) {
 
  233             throw new TskCoreException(
"Image " + objId + 
" is in use");
 
  235         imagesInProgress.add(objId);
 
  238     synchronized static private void removeImageInProgress(Long objId) {
 
  239         imagesInProgress.remove(objId);
 
  242     synchronized static private boolean isImageInProgress(Long objId) {
 
  243         return imagesInProgress.contains(objId);
 
  266             if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
 
  267                 double megabytes = ((bytes / 1024.0) / 1024.0);
 
  268                 if (megabytes <= Integer.MAX_VALUE) {
 
  269                     return (
int) Math.ceil(megabytes);
 
  279                 progress = ProgressHandle.createHandle(
 
  280                         NbBundle.getMessage(
this.getClass(), 
"ExtractUnallocAction.progress.extractUnalloc.title"), 
new Cancellable() {
 
  282                     public boolean cancel() {
 
  283                         logger.log(Level.INFO, 
"Canceling extraction of unallocated space"); 
 
  285                         if (progress != null) {
 
  286                             progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
 
  287                                     "ExtractUnallocAction.progress.displayName.cancelling.text"));
 
  292                 int MAX_BYTES = 8192;
 
  293                 byte[] buf = 
new byte[MAX_BYTES];    
 
  296                 progress.start(totalSizeinMegs);
 
  300                     currentlyProcessing = outputFileData.getFile();
 
  301                     logger.log(Level.INFO, 
"Writing Unalloc file to {0}", currentlyProcessing.getPath()); 
 
  302                     try (OutputStream outputStream = 
new FileOutputStream(currentlyProcessing)) {
 
  305                         while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
 
  306                             LayoutFile layoutFile = outputFileData.getLayouts().get(i);
 
  307                             long offsetPerFile = 0L;
 
  309                             while (offsetPerFile != layoutFile.getSize() && !
canceled) {
 
  310                                 if (++kbs % 128 == 0) {
 
  312                                     progress.progress(NbBundle.getMessage(
this.getClass(),
 
  313                                             "ExtractUnallocAction.processing.counter.msg",
 
  316                                 bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
 
  317                                 offsetPerFile += bytesRead;
 
  318                                 outputStream.write(buf, 0, bytesRead);
 
  320                             bytes += layoutFile.getSize();
 
  323                         outputStream.flush();
 
  327                         outputFileData.getFile().delete();
 
  328                         logger.log(Level.INFO, 
"Canceled extraction of {0} and deleted file", outputFileData.getFileName()); 
 
  330                         logger.log(Level.INFO, 
"Finished writing unalloc file {0}", outputFileData.getFile().getPath()); 
 
  335             } 
catch (IOException ex) {
 
  336                 logger.log(Level.WARNING, 
"Could not create Unalloc File; error writing file", ex); 
 
  338             } 
catch (TskCoreException ex) {
 
  339                 logger.log(Level.WARNING, 
"Could not create Unalloc File; error getting image info", ex); 
 
  348                 removeImageInProgress(image.getId());
 
  351                 removeVolumeInProgress(u.getFileName());
 
  356                 if (!canceled && !outputFileDataList.isEmpty()) {
 
  358                             "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
 
  359                             NbBundle.getMessage(
this.getClass(),
 
  360                                     "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
 
  361                                     outputFileDataList.get(0).getFile().getParent()));
 
  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);
 
  369             catch (java.util.concurrent.CancellationException ex) {
 
  374             List<OutputFileData> filesToExtract = 
new ArrayList<>();
 
  376             if (volume != null) {
 
  378                 filesToExtract.add(outputFileData);
 
  381                 if (hasVolumeSystem(image)) {
 
  382                     for (Volume v : getVolumes(image)) {
 
  384                         filesToExtract.add(outputFileData);
 
  388                     filesToExtract.add(outputFileData);
 
  392             if (filesToExtract.isEmpty() == 
false) {
 
  394                 List<OutputFileData> copyList = 
new ArrayList<OutputFileData>() {
 
  396                         addAll(filesToExtract);
 
  401                     outputFileData.setPath(destination);
 
  402                     if (outputFileData.getLayouts() != null && !outputFileData.getLayouts().isEmpty() && (!isVolumeInProgress(outputFileData.getFileName()))) {
 
  406                         if (outputFileData.getFile().exists()) {
 
  409                                 SwingUtilities.invokeAndWait(
new Runnable() {
 
  412                                         dialogResult.set(JOptionPane.showConfirmDialog(
new Frame(), NbBundle.getMessage(
this.getClass(),
 
  413                                                 "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
 
  414                                                 outputFileData.getFileName())));
 
  417                             } 
catch (InterruptedException | InvocationTargetException ex) {
 
  418                                logger.log(Level.SEVERE, 
"An error occured launching confirmation dialog for extract unalloc actions", ex);
 
  421                             if (dialogResult.
value == JOptionPane.YES_OPTION) {
 
  423                                 outputFileData.getFile().delete();
 
  426                                 copyList.remove(outputFileData);
 
  431                         if (outputFileData.getLayouts() == null) {
 
  432                             logger.log(Level.SEVERE, 
"Tried to get unallocated content but the list of unallocated files was null"); 
 
  433                         } 
else if (outputFileData.getLayouts().isEmpty()) {
 
  434                             logger.log(Level.WARNING, 
"No unallocated files found in volume"); 
 
  435                             copyList.remove(outputFileData);
 
  437                             logger.log(Level.WARNING, 
"Tried to get unallocated content but the volume is locked");  
 
  438                             copyList.remove(outputFileData);
 
  443                 if (!copyList.isEmpty()) {
 
  451         private void setDataFileList(List<OutputFileData> outputFileDataList) 
throws TskCoreException {
 
  454                 addImageInProgress(image.getId());
 
  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());
 
  470             if (this.outputFileDataList.isEmpty()) {
 
  471                 throw new TskCoreException(
"No unallocated files can be extracted");
 
  474             totalSizeinMegs = 
toMb(totalBytes);
 
  485     private boolean hasVolumeSystem(Image img) {
 
  487             for (Content c : img.getChildren()) {
 
  488                 if (c instanceof VolumeSystem) {
 
  492         } 
catch (TskCoreException ex) {
 
  493             logger.log(Level.SEVERE, 
"Unable to determine if image has a volume system, extraction may be incomplete", ex); 
 
  506     private List<Volume> getVolumes(Image img) {
 
  507         List<Volume> volumes = 
new ArrayList<>();
 
  509             for (Content v : img.getChildren().get(0).getChildren()) {
 
  510                 if (v instanceof Volume) {
 
  511                     volumes.add((Volume) v);
 
  514         } 
catch (TskCoreException tce) {
 
  515             logger.log(Level.WARNING, 
"Could not get volume information from image. Extraction may be incomplete", tce); 
 
  524     private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
 
  536             return new ArrayList<LayoutFile>() {
 
  553         public List<LayoutFile> 
visit(FileSystem fs) {
 
  555                 for (Content c : fs.getChildren()) {
 
  556                     if (c instanceof AbstractFile) {
 
  557                         if (((AbstractFile) c).isRoot()) {
 
  558                             return c.accept(
this);
 
  562             } 
catch (TskCoreException ex) {
 
  563                 logger.log(Level.WARNING, 
"Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex); 
 
  565             return Collections.emptyList();
 
  577         public List<LayoutFile> 
visit(VirtualDirectory vd) {
 
  579                 List<LayoutFile> layoutFiles = 
new ArrayList<>();
 
  580                 for (Content layout : vd.getChildren()) {
 
  581                     if (layout instanceof LayoutFile) {
 
  582                         layoutFiles.add((LayoutFile) layout);
 
  586             } 
catch (TskCoreException ex) {
 
  587                 logger.log(Level.WARNING, 
"Could not get list of Layout Files, failed at visiting Layout Directory", ex); 
 
  589             return Collections.emptyList();
 
  602         public List<LayoutFile> 
visit(Directory dir) {
 
  604                 for (Content c : dir.getChildren()) {
 
  606                     if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
 
  607                         return c.accept(
this);
 
  610             } 
catch (TskCoreException ex) {
 
  611                 logger.log(Level.WARNING, 
"Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex); 
 
  613             return Collections.emptyList();
 
  618             return Collections.emptyList();
 
  627     private class SortObjId implements Comparator<LayoutFile> {
 
  630         public int compare(LayoutFile o1, LayoutFile o2) {
 
  631             if (o1.getId() == o2.getId()) {
 
  634             if (o1.getId() > o2.getId()) {
 
  664             this.layoutFiles = getUnallocFiles(img);
 
  665             Collections.sort(layoutFiles, 
new SortObjId());
 
  667             this.imageId = img.getId();
 
  668             this.imageName = img.getName();
 
  669             this.fileName = this.imageName + 
"-Unalloc-" + this.imageId + 
"-" + 0 + 
".dat"; 
 
  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); 
 
  691             this.fileName = this.imageName + 
"-Unalloc-" + this.imageId + 
"-" + volumeId + 
".dat"; 
 
  693             this.layoutFiles = getUnallocFiles(volume);
 
  694             Collections.sort(layoutFiles, 
new SortObjId());
 
  700             return layoutFiles.size();
 
  705             for (LayoutFile f : layoutFiles) {
 
  711         long getSizeInBytes() {
 
  723         String getImageName() {
 
  727         List<LayoutFile> getLayouts() {
 
  731         String getFileName() {
 
  739         void setPath(String path) {
 
  740             this.fileInstance = 
new File(path + File.separator + 
this.fileName);
 
  747         private static final long serialVersionUID = 1L;
 
  755                     NbBundle.getMessage(
this.getClass(), 
"ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
 
  756             setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 
  757             setAcceptAllFileFilterUsed(
false);
 
  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"));
 
  768             super.approveSelection();
 
  777         void set(
int value) {
 
static void info(String title, String message)
 
static void error(String title, String message)
 
String getExportDirectory()