diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 36b10f81a7..2d9de3dd45 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -160,7 +160,7 @@ public class DataResultFilterNode extends FilterNode { List actions = new ArrayList(); actions.add(new NewWindowViewAction("View in New Window", vol)); actions.addAll(ShowDetailActionVisitor.getActions(vol.getLookup().lookup(Content.class))); - + actions.add(new ExtractUnallocAction("Extract Unallocated Space as Single File", vol)); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java new file mode 100644 index 0000000000..0fc49e8078 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -0,0 +1,269 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.directorytree; + +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.VolumeNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.LayoutDirectory; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extracts all the unallocated space as a single file + */ +public final class ExtractUnallocAction extends AbstractAction{ + + private static List llf; + private static Content vol; + private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); + + ExtractUnallocAction(String title, VolumeNode volume) { + super(title); + vol = volume.getLookup().lookup(Content.class); + llf = getUnallocFiles(vol); + Collections.sort(llf, new SortObjId()); + } + + /** + * Writes the unallocated files to $CaseDir/Export/ImgName-Unalloc-ImgObjectID-VolumeID.dat + * @param e + */ + @Override + public void actionPerformed(ActionEvent e) { + if (llf != null && llf.size() > 0) { + try { + String imgName = vol.getImage().getName(); //Image Name + long imgObjID = vol.getImage().getId(); //Image ID + long volumeID = vol.getId(); //Volume ID + String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat"; + //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat + File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName); + if (unalloc.exists()) { + int res = JOptionPane.showConfirmDialog(new Frame(), "The Unalloc File for this volume, " + UnallocName + " already exists, do you want to replace it?"); + if (res == JOptionPane.YES_OPTION) { + unalloc.delete(); + } else { + return; + } + } + ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc, this); + uw.execute(); + }catch (TskCoreException tce) { + logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); + } + } else { + logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + vol.getId() + ", but its list of unallocated files was empty or null"); + } + } + + private List getUnallocFiles(Content c) { + UnallocVisitor uv = new UnallocVisitor(); + logger.log(Level.INFO, "Sending out Unallocated File Visitor"); + try { + return c.getChildren().get(0).accept(uv); //Launching it on the root directory + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); + } + return null; + } + + + /** + * Private class for dispatching the file IO in a background thread. + */ + private class ExtractUnallocWorker extends SwingWorker { + + ExtractUnallocAction ua; + File path; + private ProgressHandle progress; + private boolean canceled = false; + + ExtractUnallocWorker(File path, ExtractUnallocAction ua) { + this.path = path; + this.ua = ua; + } + + @Override + protected Integer doInBackground() { + try { + progress = ProgressHandleFactory.createHandle("Extracting " + path.getName(), new Cancellable() { + @Override + public boolean cancel() { + logger.log(Level.INFO, "Canceling extraction of Unalloc file " + path.getName()); + canceled = true; + if (progress != null) { + progress.setDisplayName(path.getName() + " (Cancelling...)"); + } + return true; + } + }); + FileOutputStream fos = new FileOutputStream(path); + int MAX_BYTES = 8192; + byte[] buf = new byte[MAX_BYTES]; //read 8k at a time + logger.log(Level.INFO, "Writing Unalloc file to " + path.getPath()); + + progress.start(llf.size()); + int count = 0; + for (LayoutFile f : llf) { + long offset = 0L; + while (offset != f.getSize() && !canceled) { + offset += f.read(buf, offset, MAX_BYTES); //Offset + Bytes read + fos.write(buf); + } + progress.progress(count++); + } + progress.finish(); + fos.flush(); + fos.close(); + + if(canceled){ + path.delete(); + logger.log(Level.INFO, "Canceled extraction of " + path.getName() + " and deleted file"); + } + else{ + logger.log(Level.INFO, "Finished writing unalloc file"); + } + } catch (IOException ioe) { + logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ioe); + return -1; + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); + return -1; + } + return 1; + } + } + + private static class UnallocVisitor extends ContentVisitor.Default> { + + /** + * If the volume has no FileSystem, then it will call this method to return the single instance of unallocated space. + * @param lf the LayoutFile the visitor encountered + * @return A list of size 1 + */ + @Override + public List visit(final org.sleuthkit.datamodel.LayoutFile lf) { + return new ArrayList() { + { + add(lf); + } + }; + } + + /** + * If the visitor finds a FileSystem, it will filter the results for directories and return on the Root Dir. + * @param fs the FileSystem the visitor encountered + * @return A list containing the layout files from subsequent Visits() + */ + @Override + public List visit(FileSystem fs) { + try { + for (Content c : fs.getChildren()){ + if(((AbstractFile)c).isRoot()){ + return c.accept(this); + } + } + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), tce); + } + return null; + } + + /** + * LayoutDirectory has all the Layout(Unallocated) files + * @param ld LayoutDirectory the visitor encountered + * @return A list containing all the LayoutFile in ld + */ + @Override + public List visit(LayoutDirectory ld){ + try{ + List lflst = new ArrayList(); + for(Content layout : ld.getChildren()){ + lflst.add((LayoutFile)layout); + } + return lflst; + } catch(TskCoreException tce){ + logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory in " + vol.getId(), tce); + } + return null; + } + + /** + * The only time this visitor should ever encounter a directory is when parsing over Root + * @param dir the directory this visitor encountered + * @return A list containing LayoutFiles encountered during subsequent Visits() + */ + @Override + public List visit(Directory dir) { + try { + for (Content c : dir.getChildren()) { + if(c instanceof LayoutDirectory){ + return c.accept(this); + } + } + }catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), tce); + } + return null; + } + + @Override + protected List defaultVisit(Content cntnt) { + return null; + } + } + + private class SortObjId implements Comparator{ + + @Override + public int compare(LayoutFile o1, LayoutFile o2) { + if(o1.getId() == o2.getId()){ + return 0; + } + if(o1.getId() > o2.getId()){ + return -1; + } + else{ + return 1; + } + } + } +}