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 {
64 private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
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;
72 public ExtractUnallocAction(String title, Volume volume){
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);
85 public ExtractUnallocAction(String title, Image image)
throws NoCurrentCaseException {
88 currentImage = image.getId();
89 if (hasVolumeSystem(image)) {
90 for (Volume v : getVolumes(image)) {
91 OutputFileData outputFileData =
new OutputFileData(v);
92 filesToExtract.add(outputFileData);
95 OutputFileData outputFileData =
new OutputFileData(image);
96 filesToExtract.add(outputFileData);
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."})
113 public void actionPerformed(ActionEvent e) {
114 if (filesToExtract != null && filesToExtract.size() > 0) {
117 if (isImage && isImageInProgress(currentImage)) {
118 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
124 openCase = Case.getCurrentCaseThrows();
125 }
catch (NoCurrentCaseException ex) {
126 MessageNotifyUtil.Message.info(Bundle.ExtractAction_noOpenCase_errMsg());
129 List<OutputFileData> copyList =
new ArrayList<OutputFileData>() {
131 addAll(filesToExtract);
135 JFileChooser fileChooser =
new JFileChooser() {
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"));
144 super.approveSelection();
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);
159 if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (! isVolumeInProgress(outputFileData.getFileName()))) {
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) {
169 outputFileData.fileInstance.delete();
172 copyList.remove(outputFileData);
176 if (!isImage & !copyList.isEmpty()) {
178 ExtractUnallocWorker worker =
new ExtractUnallocWorker(outputFileData);
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()));
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");
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");
193 copyList.remove(outputFileData);
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");
197 copyList.remove(outputFileData);
206 if (isImage && !copyList.isEmpty()) {
208 ExtractUnallocWorker worker =
new ExtractUnallocWorker(copyList);
210 }
catch (Exception ex){
211 logger.log(Level.WARNING,
"Error creating ExtractUnallocWorker", ex);
212 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.imageError"));
227 private List<LayoutFile> getUnallocFiles(Content c) {
228 UnallocVisitor uv =
new UnallocVisitor();
230 for (Content contentChild : c.getChildren()) {
231 if (contentChild instanceof AbstractContent) {
232 return contentChild.accept(uv);
235 }
catch (TskCoreException tce) {
236 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce);
238 return Collections.emptyList();
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);
245 volumesInProgress.add(volumeOutputFileName);
248 synchronized static private void removeVolumeInProgress(String volumeOutputFileName){
249 volumesInProgress.remove(volumeOutputFileName);
252 synchronized static private boolean isVolumeInProgress(String volumeOutputFileName){
253 return volumesInProgress.contains(volumeOutputFileName);
256 synchronized static private void addImageInProgress(Long
id)
throws TskCoreException {
257 if(imagesInProgress.contains(
id)){
258 throw new TskCoreException(
"Image " +
id +
" is in use");
260 imagesInProgress.add(
id);
263 synchronized static private void removeImageInProgress(Long
id){
264 imagesInProgress.remove(
id);
267 synchronized static private boolean isImageInProgress(Long
id){
268 return imagesInProgress.contains(
id);
286 addVolumeInProgress(outputFileData.getFileName());
287 outputFileDataList.add(outputFileData);
288 totalBytes = outputFileData.getSizeInBytes();
289 totalSizeinMegs =
toMb(totalBytes);
293 addImageInProgress(currentImage);
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());
308 if(this.outputFileDataList.isEmpty()){
309 throw new TskCoreException(
"No unallocated files can be extracted");
312 totalSizeinMegs =
toMb(totalBytes);
316 if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
317 double Mb = ((bytes / 1024.0) / 1024.0);
318 if (Mb <= Integer.MAX_VALUE) {
319 return (
int) Math.ceil(Mb);
328 progress = ProgressHandle.createHandle(
329 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.progress.extractUnalloc.title"),
new Cancellable() {
331 public boolean cancel() {
332 logger.log(Level.INFO,
"Canceling extraction of unallocated space");
334 if (progress != null) {
335 progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
336 "ExtractUnallocAction.progress.displayName.cancelling.text"));
341 int MAX_BYTES = 8192;
342 byte[] buf =
new byte[MAX_BYTES];
345 progress.start(totalSizeinMegs);
349 currentlyProcessing = outputFileData.getFile();
350 logger.log(Level.INFO,
"Writing Unalloc file to " + currentlyProcessing.getPath());
351 OutputStream outputStream =
new FileOutputStream(currentlyProcessing);
354 while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
355 LayoutFile layoutFile = outputFileData.getLayouts().get(i);
356 long offsetPerFile = 0L;
358 while (offsetPerFile != layoutFile.getSize() && !
canceled) {
359 if (++kbs % 128 == 0) {
361 progress.progress(NbBundle.getMessage(
this.getClass(),
362 "ExtractUnallocAction.processing.counter.msg",
365 bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
366 offsetPerFile += bytesRead;
367 outputStream.write(buf, 0, bytesRead);
369 bytes += layoutFile.getSize();
372 outputStream.flush();
373 outputStream.close();
376 outputFileData.getFile().delete();
377 logger.log(Level.INFO,
"Canceled extraction of " + outputFileData.getFileName() +
" and deleted file");
379 logger.log(Level.INFO,
"Finished writing unalloc file " + outputFileData.getFile().getPath());
384 }
catch (IOException ex) {
385 logger.log(Level.WARNING,
"Could not create Unalloc File; error writing file", ex);
387 }
catch (TskCoreException ex) {
388 logger.log(Level.WARNING,
"Could not create Unalloc File; error getting image info", ex);
397 removeImageInProgress(currentImage);
400 removeVolumeInProgress(u.getFileName());
405 if (!canceled && !outputFileDataList.isEmpty()) {
407 "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
408 NbBundle.getMessage(
this.getClass(),
409 "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
410 outputFileDataList.get(0).getFile().getParent()));
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()));
417 catch (java.util.concurrent.CancellationException ex) {
429 private boolean hasVolumeSystem(Image img) {
431 for (Content c : img.getChildren()) {
432 if (c instanceof VolumeSystem) {
436 }
catch (TskCoreException ex) {
437 logger.log(Level.SEVERE,
"Unable to determine if image has a volume system, extraction may be incomplete", ex);
450 private List<Volume> getVolumes(Image img) {
451 List<Volume> volumes =
new ArrayList<>();
453 for (Content v : img.getChildren().get(0).getChildren()) {
454 if (v instanceof Volume) {
455 volumes.add((Volume) v);
458 }
catch (TskCoreException tce) {
459 logger.log(Level.WARNING,
"Could not get volume information from image. Extraction may be incomplete", tce);
468 private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
480 return new ArrayList<LayoutFile>() {
497 public List<LayoutFile>
visit(FileSystem fs) {
499 for (Content c : fs.getChildren()) {
500 if (c instanceof AbstractFile) {
501 if (((AbstractFile) c).isRoot()) {
502 return c.accept(
this);
506 }
catch (TskCoreException ex) {
507 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex);
509 return Collections.emptyList();
521 public List<LayoutFile>
visit(VirtualDirectory vd) {
523 List<LayoutFile> layoutFiles =
new ArrayList<>();
524 for (Content layout : vd.getChildren()) {
525 if (layout instanceof LayoutFile) {
526 layoutFiles.add((LayoutFile) layout);
530 }
catch (TskCoreException ex) {
531 logger.log(Level.WARNING,
"Could not get list of Layout Files, failed at visiting Layout Directory", ex);
533 return Collections.emptyList();
546 public List<LayoutFile>
visit(Directory dir) {
548 for (Content c : dir.getChildren()) {
550 if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
551 return c.accept(
this);
554 }
catch (TskCoreException ex) {
555 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex);
557 return Collections.emptyList();
562 return Collections.emptyList();
571 private class SortObjId implements Comparator<LayoutFile> {
574 public int compare(LayoutFile o1, LayoutFile o2) {
575 if (o1.getId() == o2.getId()) {
578 if (o1.getId() > o2.getId()) {
608 this.layoutFiles = getUnallocFiles(img);
609 Collections.sort(layoutFiles,
new SortObjId());
611 this.imageId = img.getId();
612 this.imageName = img.getName();
613 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + 0 +
".dat";
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);
635 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + volumeId +
".dat";
636 this.fileInstance =
new File(Case.getCurrentCaseThrows().getExportDirectory() + File.separator + this.
fileName);
637 this.layoutFiles = getUnallocFiles(volume);
638 Collections.sort(layoutFiles,
new SortObjId());
644 return layoutFiles.size();
649 for (LayoutFile f : layoutFiles) {
655 long getSizeInBytes() {
667 String getImageName() {
671 List<LayoutFile> getLayouts() {
675 String getFileName() {
683 void setPath(String path) {
684 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()