Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractUnallocAction.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2011-2018 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.directorytree;
20
21import java.awt.Component;
22import java.awt.Frame;
23import java.awt.event.ActionEvent;
24import java.io.File;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.io.OutputStream;
28import java.lang.reflect.InvocationTargetException;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32import java.util.List;
33import java.util.Set;
34import java.util.HashSet;
35import java.util.concurrent.ExecutionException;
36import java.util.logging.Level;
37import javax.swing.AbstractAction;
38import javax.swing.JFileChooser;
39import static javax.swing.JFileChooser.SAVE_DIALOG;
40import javax.swing.JOptionPane;
41import javax.swing.SwingUtilities;
42import javax.swing.SwingWorker;
43import org.netbeans.api.progress.ProgressHandle;
44import org.openide.util.Cancellable;
45import org.openide.util.NbBundle;
46import org.sleuthkit.autopsy.casemodule.Case;
47import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
48import org.sleuthkit.autopsy.coreutils.Logger;
49import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
50import org.sleuthkit.autopsy.guiutils.JFileChooserFactory;
51import org.sleuthkit.datamodel.AbstractContent;
52import org.sleuthkit.datamodel.AbstractFile;
53import org.sleuthkit.datamodel.Content;
54import org.sleuthkit.datamodel.ContentVisitor;
55import org.sleuthkit.datamodel.Directory;
56import org.sleuthkit.datamodel.FileSystem;
57import org.sleuthkit.datamodel.Image;
58import org.sleuthkit.datamodel.LayoutFile;
59import org.sleuthkit.datamodel.TskCoreException;
60import org.sleuthkit.datamodel.VirtualDirectory;
61import org.sleuthkit.datamodel.Volume;
62import org.sleuthkit.datamodel.VolumeSystem;
63
67final class ExtractUnallocAction extends AbstractAction {
68
69 private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
70 private static final long serialVersionUID = 1L;
71
72 private static final Set<String> volumesInProgress = new HashSet<>();
73 private static final Set<Long> imagesInProgress = new HashSet<>();
74 private static String userDefinedExportPath;
75
76 private final Volume volume;
77 private final Image image;
78
79 private final JFileChooserFactory chooserFactory;
80
87 ExtractUnallocAction(String title, Volume volume) {
88 this(title, null, volume);
89
90 }
91
100 ExtractUnallocAction(String title, Image image) {
101 this(title, image, null);
102 }
103
104 ExtractUnallocAction(String title, Image image, Volume volume) {
105 super(title);
106
107 this.volume = volume;
108 this.image = image;
109
110 chooserFactory = new JFileChooserFactory(CustomFileChooser.class);
111 }
112
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."})
125 @Override
126 public void actionPerformed(ActionEvent event) {
127
128 Case openCase;
129 try {
130 openCase = Case.getCurrentCaseThrows();
131 } catch (NoCurrentCaseException ex) {
132 MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg());
133 return;
134 }
135
136 JFileChooser fileChooser = chooserFactory.getChooser();
137
138 fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
139 if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog((Component) event.getSource())) {
140 return;
141 }
142
143 String destination = fileChooser.getSelectedFile().getPath();
144 updateExportDirectory(destination, openCase);
145
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.");
149 return;
150 }
151
152 ExtractUnallocWorker worker = new ExtractUnallocWorker(openCase, destination);
153 worker.execute();
154 }
155
163 private String getExportDirectory(Case openCase) {
164 String caseExportPath = openCase.getExportDirectory();
165
166 if (userDefinedExportPath == null) {
167 return caseExportPath;
168 }
169
170 File file = new File(userDefinedExportPath);
171 if (file.exists() == false || file.isDirectory() == false) {
172 return caseExportPath;
173 }
174
175 return userDefinedExportPath;
176 }
177
187 private void updateExportDirectory(String exportPath, Case openCase) {
188 if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
189 userDefinedExportPath = null;
190 } else {
191 userDefinedExportPath = exportPath;
192 }
193 }
194
202 private List<LayoutFile> getUnallocFiles(Content content) {
203 UnallocVisitor unallocVisitor = new UnallocVisitor();
204 try {
205 for (Content contentChild : content.getChildren()) {
206 if (contentChild instanceof AbstractContent) {
207 return contentChild.accept(unallocVisitor); //call on first non-artifact child added to database
208 }
209 }
210 } catch (TskCoreException tce) {
211 logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); //NON-NLS
212 }
213 return Collections.emptyList();
214 }
215
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);
219 }
220 volumesInProgress.add(volumeOutputFileName);
221 }
222
223 synchronized static private void removeVolumeInProgress(String volumeOutputFileName) {
224 volumesInProgress.remove(volumeOutputFileName);
225 }
226
227 synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) {
228 return volumesInProgress.contains(volumeOutputFileName);
229 }
230
231 synchronized static private void addImageInProgress(Long objId) throws TskCoreException {
232 if (imagesInProgress.contains(objId)) {
233 throw new TskCoreException("Image " + objId + " is in use");
234 }
235 imagesInProgress.add(objId);
236 }
237
238 synchronized static private void removeImageInProgress(Long objId) {
239 imagesInProgress.remove(objId);
240 }
241
242 synchronized static private boolean isImageInProgress(Long objId) {
243 return imagesInProgress.contains(objId);
244 }
245
249 private class ExtractUnallocWorker extends SwingWorker<Integer, Integer> {
250
251 private ProgressHandle progress;
252 private boolean canceled = false;
253 private final List<OutputFileData> outputFileDataList = new ArrayList<>();
255 private int totalSizeinMegs;
256 long totalBytes = 0;
257 private final String destination;
258 private Case openCase;
259
260 ExtractUnallocWorker(Case openCase, String destination) {
261 this.destination = destination;
262 this.openCase = openCase;
263 }
264
265 private int toMb(long bytes) {
266 if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) {
267 double megabytes = ((bytes / 1024.0) / 1024.0);//Bytes -> Megabytes
268 if (megabytes <= Integer.MAX_VALUE) {
269 return (int) Math.ceil(megabytes);
270 }
271 }
272 return 0;
273 }
274
275 @Override
276 protected Integer doInBackground() {
277 try {
279 progress = ProgressHandle.createHandle(
280 NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.progress.extractUnalloc.title"), new Cancellable() {
281 @Override
282 public boolean cancel() {
283 logger.log(Level.INFO, "Canceling extraction of unallocated space"); //NON-NLS
284 canceled = true;
285 if (progress != null) {
286 progress.setDisplayName(NbBundle.getMessage(this.getClass(),
287 "ExtractUnallocAction.progress.displayName.cancelling.text"));
288 }
289 return true;
290 }
291 });
292 int MAX_BYTES = 8192;
293 byte[] buf = new byte[MAX_BYTES]; //read 8kb at a time
294
295 //Begin the actual File IO
297 int kbs = 0; //Each completion of the while loop adds one to kbs. 16kb * 64 = 1mb.
298 int mbs = 0; //Increments every 128th tick of kbs
299 for (OutputFileData outputFileData : this.outputFileDataList) {
300 currentlyProcessing = outputFileData.getFile();
301 logger.log(Level.INFO, "Writing Unalloc file to {0}", currentlyProcessing.getPath()); //NON-NLS
302 try (OutputStream outputStream = new FileOutputStream(currentlyProcessing)) {
303 long bytes = 0;
304 int i = 0;
305 while (i < outputFileData.getLayouts().size() && bytes != outputFileData.getSizeInBytes()) {
306 LayoutFile layoutFile = outputFileData.getLayouts().get(i);
307 long offsetPerFile = 0L;
308 int bytesRead;
309 while (offsetPerFile != layoutFile.getSize() && !canceled) {
310 if (++kbs % 128 == 0) {
311 mbs++;
312 progress.progress(NbBundle.getMessage(this.getClass(),
313 "ExtractUnallocAction.processing.counter.msg",
314 mbs, totalSizeinMegs), mbs - 1);
315 }
316 bytesRead = layoutFile.read(buf, offsetPerFile, MAX_BYTES);
317 offsetPerFile += bytesRead;
318 outputStream.write(buf, 0, bytesRead);
319 }
320 bytes += layoutFile.getSize();
321 i++;
322 }
323 outputStream.flush();
324 }
325
326 if (canceled) {
327 outputFileData.getFile().delete();
328 logger.log(Level.INFO, "Canceled extraction of {0} and deleted file", outputFileData.getFileName()); //NON-NLS
329 } else {
330 logger.log(Level.INFO, "Finished writing unalloc file {0}", outputFileData.getFile().getPath()); //NON-NLS
331 }
332 }
333 progress.finish();
334
335 } catch (IOException ex) {
336 logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ex); //NON-NLS
337 return -1;
338 } catch (TskCoreException ex) {
339 logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", ex); //NON-NLS
340 return -1;
341 }
342 return 1;
343 }
344
345 @Override
346 protected void done() {
347 if (image != null) {
348 removeImageInProgress(image.getId());
349 }
351 removeVolumeInProgress(u.getFileName());
352 }
353
354 try {
355 get();
356 if (!canceled && !outputFileDataList.isEmpty()) {
357 MessageNotifyUtil.Notify.info(NbBundle.getMessage(this.getClass(),
358 "ExtractUnallocAction.done.notifyMsg.completedExtract.title"),
359 NbBundle.getMessage(this.getClass(),
360 "ExtractUnallocAction.done.notifyMsg.completedExtract.msg",
361 outputFileDataList.get(0).getFile().getParent()));
362 }
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);
368 } // catch and ignore if we were cancelled
369 catch (java.util.concurrent.CancellationException ex) {
370 }
371 }
372
373 private void initalizeFilesToExtract() throws TskCoreException {
374 List<OutputFileData> filesToExtract = new ArrayList<>();
375
376 if (volume != null) {
377 OutputFileData outputFileData = new OutputFileData(volume, openCase);
378 filesToExtract.add(outputFileData);
379
380 } else {
381 if (hasVolumeSystem(image)) {
382 for (Volume v : getVolumes(image)) {
383 OutputFileData outputFileData = new OutputFileData(v, openCase);
384 filesToExtract.add(outputFileData);
385 }
386 } else {
387 OutputFileData outputFileData = new OutputFileData(image, openCase);
388 filesToExtract.add(outputFileData);
389 }
390 }
391
392 if (filesToExtract.isEmpty() == false) {
393
394 List<OutputFileData> copyList = new ArrayList<OutputFileData>() {
395 {
396 addAll(filesToExtract);
397 }
398 };
399
400 for (OutputFileData outputFileData : filesToExtract) {
401 outputFileData.setPath(destination);
402 if (outputFileData.getLayouts() != null && !outputFileData.getLayouts().isEmpty() && (!isVolumeInProgress(outputFileData.getFileName()))) {
403 //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat
404
405 // Check if there is already a file with this name
406 if (outputFileData.getFile().exists()) {
407 final Result dialogResult = new Result();
408 try {
409 SwingUtilities.invokeAndWait(new Runnable() {
410 @Override
411 public void run() {
412 dialogResult.set(JOptionPane.showConfirmDialog(new Frame(), NbBundle.getMessage(this.getClass(),
413 "ExtractUnallocAction.confDlg.unallocFileAlreadyExist.msg",
414 outputFileData.getFileName())));
415 }
416 });
417 } catch (InterruptedException | InvocationTargetException ex) {
418 logger.log(Level.SEVERE, "An error occured launching confirmation dialog for extract unalloc actions", ex);
419 }
420
421 if (dialogResult.value == JOptionPane.YES_OPTION) {
422 // If the user wants to overwrite, delete the exising output file
423 outputFileData.getFile().delete();
424 } else {
425 // Otherwise remove it from the list of output files
426 copyList.remove(outputFileData);
427 }
428 }
429 } else {
430 // The output file for this volume could not be created for one of the following reasons
431 if (outputFileData.getLayouts() == null) {
432 logger.log(Level.SEVERE, "Tried to get unallocated content but the list of unallocated files was null"); //NON-NLS
433 } else if (outputFileData.getLayouts().isEmpty()) {
434 logger.log(Level.WARNING, "No unallocated files found in volume"); //NON-NLS
435 copyList.remove(outputFileData);
436 } else {
437 logger.log(Level.WARNING, "Tried to get unallocated content but the volume is locked"); // NON_NLS
438 copyList.remove(outputFileData);
439 }
440 }
441 }
442
443 if (!copyList.isEmpty()) {
444
445 setDataFileList(copyList);
446
447 }
448 }
449 }
450
451 private void setDataFileList(List<OutputFileData> outputFileDataList) throws TskCoreException {
452
453 if (image != null) {
454 addImageInProgress(image.getId());
455 }
456
457 //Getting the total megs this worker is going to be doing
458 for (OutputFileData outputFileData : outputFileDataList) {
459 try {
460 // If a volume is locked, skip it but continue trying to process any other requested volumes
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());
466 }
467 }
468
469 // If we don't have anything to output (because of locking), throw an exception
470 if (this.outputFileDataList.isEmpty()) {
471 throw new TskCoreException("No unallocated files can be extracted");
472 }
473
474 totalSizeinMegs = toMb(totalBytes);
475 }
476 }
477
485 private boolean hasVolumeSystem(Image img) {
486 try {
487 for (Content c : img.getChildren()) {
488 if (c instanceof VolumeSystem) {
489 return true;
490 }
491 }
492 } catch (TskCoreException ex) {
493 logger.log(Level.SEVERE, "Unable to determine if image has a volume system, extraction may be incomplete", ex); //NON-NLS
494 }
495 return false;
496 }
497
506 private List<Volume> getVolumes(Image img) {
507 List<Volume> volumes = new ArrayList<>();
508 try {
509 for (Content v : img.getChildren().get(0).getChildren()) {
510 if (v instanceof Volume) {
511 volumes.add((Volume) v);
512 }
513 }
514 } catch (TskCoreException tce) {
515 logger.log(Level.WARNING, "Could not get volume information from image. Extraction may be incomplete", tce); //NON-NLS
516 }
517 return volumes;
518 }
519
524 private static class UnallocVisitor extends ContentVisitor.Default<List<LayoutFile>> {
525
534 @Override
535 public List<LayoutFile> visit(final org.sleuthkit.datamodel.LayoutFile lf) {
536 return new ArrayList<LayoutFile>() {
537 {
538 add(lf);
539 }
540 };
541 }
542
552 @Override
553 public List<LayoutFile> visit(FileSystem fs) {
554 try {
555 for (Content c : fs.getChildren()) {
556 if (c instanceof AbstractFile) {
557 if (((AbstractFile) c).isRoot()) {
558 return c.accept(this);
559 }
560 }
561 }
562 } catch (TskCoreException ex) {
563 logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), ex); //NON-NLS
564 }
565 return Collections.emptyList();
566 }
567
576 @Override
577 public List<LayoutFile> visit(VirtualDirectory vd) {
578 try {
579 List<LayoutFile> layoutFiles = new ArrayList<>();
580 for (Content layout : vd.getChildren()) {
581 if (layout instanceof LayoutFile) {
582 layoutFiles.add((LayoutFile) layout);
583 }
584 }
585 return layoutFiles;
586 } catch (TskCoreException ex) {
587 logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory", ex); //NON-NLS
588 }
589 return Collections.emptyList();
590 }
591
601 @Override
602 public List<LayoutFile> visit(Directory dir) {
603 try {
604 for (Content c : dir.getChildren()) {
605 // Only the $Unalloc dir will contain unallocated files
606 if ((c instanceof VirtualDirectory) && (c.getName().equals(VirtualDirectory.NAME_UNALLOC))) {
607 return c.accept(this);
608 }
609 }
610 } catch (TskCoreException ex) {
611 logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), ex); //NON-NLS
612 }
613 return Collections.emptyList();
614 }
615
616 @Override
617 protected List<LayoutFile> defaultVisit(Content cntnt) {
618 return Collections.emptyList();
619 }
620 }
621
627 private class SortObjId implements Comparator<LayoutFile> {
628
629 @Override
630 public int compare(LayoutFile o1, LayoutFile o2) {
631 if (o1.getId() == o2.getId()) {
632 return 0;
633 }
634 if (o1.getId() > o2.getId()) {
635 return 1;
636 } else {
637 return -1;
638 }
639 }
640 }
641
646 private class OutputFileData {
647
648 private final List<LayoutFile> layoutFiles;
649 private final long sizeInBytes;
650 private long volumeId;
651 private long imageId;
652 private String imageName;
653 private final String fileName;
654 private File fileInstance;
655
663 OutputFileData(Image img, Case openCase) {
664 this.layoutFiles = getUnallocFiles(img);
665 Collections.sort(layoutFiles, new SortObjId());
666 this.volumeId = 0;
667 this.imageId = img.getId();
668 this.imageName = img.getName();
669 this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + 0 + ".dat"; //NON-NLS
670 this.fileInstance = new File(openCase.getExportDirectory() + File.separator + this.fileName);
671 this.sizeInBytes = calcSizeInBytes();
672 }
673
681 OutputFileData(Volume volume, Case openCase) {
682 try {
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); //NON-NLS
688 this.imageName = "";
689 this.imageId = 0;
690 }
691 this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + volumeId + ".dat"; //NON-NLS
692 this.fileInstance = new File(openCase.getExportDirectory() + File.separator + this.fileName);
693 this.layoutFiles = getUnallocFiles(volume);
694 Collections.sort(layoutFiles, new SortObjId());
695 this.sizeInBytes = calcSizeInBytes();
696 }
697
698 //Getters
699 int size() {
700 return layoutFiles.size();
701 }
702
703 private long calcSizeInBytes() {
704 long size = 0L;
705 for (LayoutFile f : layoutFiles) {
706 size += f.getSize();
707 }
708 return size;
709 }
710
711 long getSizeInBytes() {
712 return this.sizeInBytes;
713 }
714
715 long getVolumeId() {
716 return this.volumeId;
717 }
718
719 long getImageId() {
720 return this.imageId;
721 }
722
723 String getImageName() {
724 return this.imageName;
725 }
726
727 List<LayoutFile> getLayouts() {
728 return this.layoutFiles;
729 }
730
731 String getFileName() {
732 return this.fileName;
733 }
734
735 File getFile() {
736 return this.fileInstance;
737 }
738
739 void setPath(String path) {
740 this.fileInstance = new File(path + File.separator + this.fileName);
741 }
742 }
743
744 // A Custome JFileChooser for this Action Class.
745 public static class CustomFileChooser extends JFileChooser {
746
747 private static final long serialVersionUID = 1L;
748
750 initalize();
751 }
752
753 private void initalize() {
754 setDialogTitle(
755 NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg"));
756 setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
757 setAcceptAllFileFilterUsed(false);
758 }
759
760 @Override
761 public void approveSelection() {
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"));
766 return;
767 }
768 super.approveSelection();
769 }
770 }
771
772 // Small helper class for use with SwingUtilities involkAndWait to get
773 // the result from the launching of the JOptionPane.
774 private class Result {
775 private int value;
776
777 void set(int value) {
778 this.value = value;
779 }
780
781 int value() {
782 return value;
783 }
784 }
785}
List< LayoutFile > visit(final org.sleuthkit.datamodel.LayoutFile lf)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.