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;
62 final class ExtractUnallocAction
extends AbstractAction {
63 private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
65 private final List<OutputFileData> filesToExtract =
new ArrayList<>();
66 private static final Set<String> volumesInProgress =
new HashSet<>();
67 private static final Set<Long> imagesInProgress =
new HashSet<>();
68 private long currentImage = 0L;
69 private final boolean isImage;
71 public ExtractUnallocAction(String title, Volume volume) {
74 OutputFileData outputFileData =
new OutputFileData(volume);
75 filesToExtract.add(outputFileData);
78 public ExtractUnallocAction(String title, Image image) {
81 currentImage = image.getId();
82 if (hasVolumeSystem(image)) {
83 for (Volume v : getVolumes(image)) {
84 OutputFileData outputFileData =
new OutputFileData(v);
85 filesToExtract.add(outputFileData);
88 OutputFileData outputFileData =
new OutputFileData(image);
89 filesToExtract.add(outputFileData);
99 @NbBundle.Messages({
"# {0} - fileName",
100 "ExtractUnallocAction.volumeInProgress=Already extracting unallocated space into {0} - will skip this volume",
101 "ExtractUnallocAction.volumeError=Error extracting unallocated space from volume",
102 "ExtractUnallocAction.noFiles=No unallocated files found on volume",
103 "ExtractUnallocAction.imageError=Error extracting unallocated space from image"})
105 public void actionPerformed(ActionEvent e) {
106 if (filesToExtract != null && filesToExtract.size() > 0) {
109 if (isImage && isImageInProgress(currentImage)) {
110 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg"));
114 List<OutputFileData> copyList =
new ArrayList<OutputFileData>() {
116 addAll(filesToExtract);
120 JFileChooser fileChooser =
new JFileChooser() {
122 public void approveSelection() {
123 File f = getSelectedFile();
124 if (!f.exists() && getDialogType() == SAVE_DIALOG || !f.canWrite()) {
125 JOptionPane.showMessageDialog(
this, NbBundle.getMessage(
this.getClass(),
126 "ExtractUnallocAction.msgDlg.folderDoesntExist.msg"));
129 super.approveSelection();
133 fileChooser.setCurrentDirectory(
new File(Case.getCurrentCase().getExportDirectory()));
134 fileChooser.setDialogTitle(
135 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
136 fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
137 fileChooser.setAcceptAllFileFilterUsed(
false);
138 int returnValue = fileChooser.showSaveDialog((Component) e.getSource());
139 if (returnValue == JFileChooser.APPROVE_OPTION) {
140 String destination = fileChooser.getSelectedFile().getPath();
141 for (OutputFileData outputFileData : filesToExtract) {
142 outputFileData.setPath(destination);
144 if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (! isVolumeInProgress(outputFileData.getFileName()))) {
148 if (outputFileData.fileInstance.exists()) {
149 int res = JOptionPane.showConfirmDialog(
new Frame(), NbBundle.getMessage(
this.getClass(),
150 "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
151 outputFileData.getFileName()));
152 if (res == JOptionPane.YES_OPTION) {
154 outputFileData.fileInstance.delete();
157 copyList.remove(outputFileData);
161 if (!isImage & !copyList.isEmpty()) {
163 ExtractUnallocWorker worker =
new ExtractUnallocWorker(outputFileData);
165 }
catch (Exception ex){
166 logger.log(Level.WARNING,
"Already extracting unallocated space into " + outputFileData.getFileName());
167 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.volumeInProgress", outputFileData.getFileName()));
172 if (outputFileData.layoutFiles == null){
173 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.volumeError"));
174 logger.log(Level.SEVERE,
"Tried to get unallocated content but the list of unallocated files was null");
175 }
else if (outputFileData.layoutFiles.isEmpty()){
176 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.noFiles"));
177 logger.log(Level.WARNING,
"No unallocated files found in volume");
178 copyList.remove(outputFileData);
180 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.volumeInProgress", outputFileData.getFileName()));
181 logger.log(Level.WARNING,
"Tried to get unallocated content but the volume is locked");
182 copyList.remove(outputFileData);
191 if (isImage && !copyList.isEmpty()) {
193 ExtractUnallocWorker worker =
new ExtractUnallocWorker(copyList);
195 }
catch (Exception ex){
196 logger.log(Level.WARNING,
"Error creating ExtractUnallocWorker", ex);
197 MessageNotifyUtil.Message.info(NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.imageError"));
212 private List<LayoutFile> getUnallocFiles(Content c) {
213 UnallocVisitor uv =
new UnallocVisitor();
215 for (Content contentChild : c.getChildren()) {
216 if (contentChild instanceof AbstractContent) {
217 return contentChild.accept(uv);
220 }
catch (TskCoreException tce) {
221 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce);
223 return Collections.emptyList();
226 synchronized static private void addVolumeInProgress(String volumeOutputFileName)
throws TskCoreException {
227 if(volumesInProgress.contains(volumeOutputFileName)){
228 throw new TskCoreException(
"Already writing unallocated space to " + volumeOutputFileName);
230 volumesInProgress.add(volumeOutputFileName);
233 synchronized static private void removeVolumeInProgress(String volumeOutputFileName){
234 volumesInProgress.remove(volumeOutputFileName);
237 synchronized static private boolean isVolumeInProgress(String volumeOutputFileName){
238 return volumesInProgress.contains(volumeOutputFileName);
241 synchronized static private void addImageInProgress(Long
id)
throws TskCoreException {
242 if(imagesInProgress.contains(
id)){
243 throw new TskCoreException(
"Image " +
id +
" is in use");
245 imagesInProgress.add(
id);
248 synchronized static private void removeImageInProgress(Long
id){
249 imagesInProgress.remove(
id);
252 synchronized static private boolean isImageInProgress(Long
id){
253 return imagesInProgress.contains(
id);
271 addVolumeInProgress(outputFileData.getFileName());
272 outputFileDataList.add(outputFileData);
273 totalBytes = outputFileData.getSizeInBytes();
274 totalSizeinMegs =
toMb(totalBytes);
278 addImageInProgress(currentImage);
284 addVolumeInProgress(outputFileData.getFileName());
285 totalBytes += outputFileData.getSizeInBytes();
286 this.outputFileDataList.add(outputFileData);
287 }
catch (TskCoreException ex){
288 logger.log(Level.WARNING,
"Already extracting data into " + outputFileData.getFileName());
293 if(this.outputFileDataList.isEmpty()){
294 throw new TskCoreException(
"No unallocated files can be extracted");
297 totalSizeinMegs =
toMb(totalBytes);
301 if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
302 double Mb = ((bytes / 1024.0) / 1024.0);
303 if (Mb <= Integer.MAX_VALUE) {
304 return (
int) Math.ceil(Mb);
313 progress = ProgressHandle.createHandle(
314 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.progress.extractUnalloc.title"),
new Cancellable() {
316 public boolean cancel() {
317 logger.log(Level.INFO,
"Canceling extraction of unallocated space");
319 if (progress != null) {
320 progress.setDisplayName(NbBundle.getMessage(
this.getClass(),
321 "ExtractUnallocAction.progress.displayName.cancelling.text"));
326 int MAX_BYTES = 8192;
327 byte[] buf =
new byte[MAX_BYTES];
330 progress.start(totalSizeinMegs);
334 currentlyProcessing = outputFileData.getFile();
335 logger.log(Level.INFO,
"Writing Unalloc file to " + currentlyProcessing.getPath());
336 OutputStream outputStream =
new FileOutputStream(currentlyProcessing);
339 while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
340 LayoutFile layoutFile = outputFileData.getLayouts().get(i);
341 long offsetPerFile = 0L;
343 while (offsetPerFile != layoutFile.getSize() && !
canceled) {
344 if (++kbs % 128 == 0) {
346 progress.progress(NbBundle.getMessage(
this.getClass(),
347 "ExtractUnallocAction.processing.counter.msg",
350 bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
351 offsetPerFile += bytesRead;
352 outputStream.write(buf, 0, bytesRead);
354 bytes += layoutFile.getSize();
357 outputStream.flush();
358 outputStream.close();
361 outputFileData.getFile().delete();
362 logger.log(Level.INFO,
"Canceled extraction of " + outputFileData.getFileName() +
" and deleted file");
364 logger.log(Level.INFO,
"Finished writing unalloc file " + outputFileData.getFile().getPath());
369 }
catch (IOException ex) {
370 logger.log(Level.WARNING,
"Could not create Unalloc File; error writing file", ex);
372 }
catch (TskCoreException ex) {
373 logger.log(Level.WARNING,
"Could not create Unalloc File; error getting image info", ex);
382 removeImageInProgress(currentImage);
385 removeVolumeInProgress(u.getFileName());
390 if (!canceled && !outputFileDataList.isEmpty()) {
392 "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
393 NbBundle.getMessage(
this.getClass(),
394 "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
395 outputFileDataList.get(0).getFile().getParent()));
397 }
catch (InterruptedException | ExecutionException ex) {
399 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.done.errMsg.title"),
400 NbBundle.getMessage(
this.getClass(),
"ExtractUnallocAction.done.errMsg.msg", ex.getMessage()));
402 catch (java.util.concurrent.CancellationException ex) {
414 private boolean hasVolumeSystem(Image img) {
416 for (Content c : img.getChildren()) {
417 if (c instanceof VolumeSystem) {
421 }
catch (TskCoreException ex) {
422 logger.log(Level.SEVERE,
"Unable to determine if image has a volume system, extraction may be incomplete", ex);
435 private List<Volume> getVolumes(Image img) {
436 List<Volume> volumes =
new ArrayList<>();
438 for (Content v : img.getChildren().get(0).getChildren()) {
439 if (v instanceof Volume) {
440 volumes.add((Volume) v);
443 }
catch (TskCoreException tce) {
444 logger.log(Level.WARNING,
"Could not get volume information from image. Extraction may be incomplete", tce);
453 private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
465 return new ArrayList<LayoutFile>() {
482 public List<LayoutFile>
visit(FileSystem fs) {
484 for (Content c : fs.getChildren()) {
485 if (c instanceof AbstractFile) {
486 if (((AbstractFile) c).isRoot()) {
487 return c.accept(
this);
491 }
catch (TskCoreException ex) {
492 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex);
494 return Collections.emptyList();
506 public List<LayoutFile>
visit(VirtualDirectory vd) {
508 List<LayoutFile> layoutFiles =
new ArrayList<>();
509 for (Content layout : vd.getChildren()) {
510 if (layout instanceof LayoutFile) {
511 layoutFiles.add((LayoutFile) layout);
515 }
catch (TskCoreException ex) {
516 logger.log(Level.WARNING,
"Could not get list of Layout Files, failed at visiting Layout Directory", ex);
518 return Collections.emptyList();
531 public List<LayoutFile>
visit(Directory dir) {
533 for (Content c : dir.getChildren()) {
535 if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
536 return c.accept(
this);
539 }
catch (TskCoreException ex) {
540 logger.log(Level.WARNING,
"Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex);
542 return Collections.emptyList();
547 return Collections.emptyList();
556 private class SortObjId implements Comparator<LayoutFile> {
559 public int compare(LayoutFile o1, LayoutFile o2) {
560 if (o1.getId() == o2.getId()) {
563 if (o1.getId() > o2.getId()) {
591 this.layoutFiles = getUnallocFiles(img);
592 Collections.sort(layoutFiles,
new SortObjId());
594 this.imageId = img.getId();
595 this.imageName = img.getName();
596 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + 0 +
".dat";
608 this.imageName = volume.getDataSource().getName();
609 this.imageId = volume.getDataSource().getId();
610 this.volumeId = volume.getId();
611 }
catch (TskCoreException tce) {
612 logger.log(Level.WARNING,
"Unable to properly create ExtractUnallocAction, extraction may be incomplete", tce);
616 this.fileName = this.imageName +
"-Unalloc-" + this.imageId +
"-" + volumeId +
".dat";
617 this.fileInstance =
new File(Case.getCurrentCase().getExportDirectory() + File.separator + this.
fileName);
618 this.layoutFiles = getUnallocFiles(volume);
619 Collections.sort(layoutFiles,
new SortObjId());
625 return layoutFiles.size();
630 for (LayoutFile f : layoutFiles) {
636 long getSizeInBytes() {
648 String getImageName() {
652 List<LayoutFile> getLayouts() {
656 String getFileName() {
664 void setPath(String path) {
665 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 getCurrentCase()
String getExportDirectory()