/*
* Autopsy Forensic Browser
*
* Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.ingest;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.io.File;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.SimpleFormatter;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.Timer;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.events.AutopsyEvent;
/**
* Monitors disk space and memory and cancels ingest if disk space runs low.
* <p>
* Note: This should be a singleton and currrently is used as such, with the
* only instance residing in the IngestManager class.
*/
public final class IngestMonitor {
public static final int DISK_FREE_SPACE_UNKNOWN = -1;
private static final int INITIAL_INTERVAL_MS = 60000; //1 min.
private static final int MAX_LOG_FILES = 3;
private static final java.util.logging.Logger MONITOR_LOGGER = java.util.logging.Logger.getLogger("monitor"); //NON-NLS
private final Logger logger = Logger.getLogger(IngestMonitor.class.getName());
private Timer timer;
private MonitorTimerAction timerAction;
/**
* Constructs an object that monitors disk space and memory and cancels
* ingest if disk space runs low.
*/
IngestMonitor() {
/*
* Setup a separate memory usage logger.
*/
try {
FileHandler monitorLogHandler = new FileHandler(PlatformUtil.getUserDirectory().getAbsolutePath() + "/var/log/monitor.log", 0, MAX_LOG_FILES); //NON-NLS
monitorLogHandler.setFormatter(new SimpleFormatter());
monitorLogHandler.setEncoding(PlatformUtil.getLogFileEncoding());
MONITOR_LOGGER.setUseParentHandlers(false);
MONITOR_LOGGER.addHandler(monitorLogHandler);
} catch (IOException | SecurityException ex) {
logger.log(Level.SEVERE, "Failed to create memory usage logger", ex); //NON-NLS
}
}
/**
* Starts the ingest timerAction.
*/
void start() {
timerAction = new MonitorTimerAction();
timer = new Timer(INITIAL_INTERVAL_MS, timerAction);
timer.start();
}
/**
* Stops the ingest timerAction.
*/
void stop() {
if (null != timer) {
timer.stop();
}
}
/**
* Checks whether or not the ingest timerAction is running
*
* @return True or false
*/
boolean isRunning() {
return (null != timer && timer.isRunning());
}
/**
* Gets the free space, in bytes, of the drive where the case folder for the
* current case resides.
*
* @return Free space in bytes or -1 if free sapce could not be determined.
*/
long getFreeSpace() {
try {
return timerAction.getFreeSpace();
} catch (SecurityException e) {
logger.log(Level.WARNING, "Error checking for free disk space on ingest data drive", e); //NON-NLS
return DISK_FREE_SPACE_UNKNOWN;
}
}
/**
* An action that is called every time the ingest monitor's timer expires.
* It does the actual monitoring.
*/
private class MonitorTimerAction implements ActionListener {
private final static long MIN_FREE_DISK_SPACE = 100L * 1024 * 1024; // 100MB
private File root;
MonitorTimerAction() {
findRootDirectoryForCurrentCase();
Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent evt) -> {
if (evt instanceof AutopsyEvent) {
AutopsyEvent event = (AutopsyEvent) evt;
if (AutopsyEvent.SourceType.LOCAL == event.getSourceType() && event.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) {
/*
* The new value of the event will be non-null if a new
* case has been opened.
*/
if (null != evt.getNewValue()) {
findRootDirectoryForCurrentCase((Case) evt.getNewValue());
}
}
}
});
}
/**
* Determines the root directory of the case folder for the current case
* and sets it as the directory to monitor.
*/
private void findRootDirectoryForCurrentCase() {
try {
Case currentCase = Case.getCurrentCase();
findRootDirectoryForCurrentCase(currentCase);
} catch (IllegalStateException unused) {
/*
* Case.getCurrentCase() throws IllegalStateException when there
* is no case.
*/
root = new File(File.separator);
logMonitoredRootDirectory();
}
}
/**
* Determines the root directory of the case folder for the current case
* and sets it as the directory to monitor.
*
* @param currentCase The current case.
*/
private void findRootDirectoryForCurrentCase(Case currentCase) {
File curDir = new File(currentCase.getCaseDirectory());
File parentDir = curDir.getParentFile();
while (null != parentDir) {
curDir = parentDir;
parentDir = curDir.getParentFile();
}
root = curDir;
logMonitoredRootDirectory();
}
/**
* Writes an info message to the Autopsy log identifying the root
* directory being monitored.
*/
private void logMonitoredRootDirectory() {
logger.log(Level.INFO, "Monitoring disk space of {0}", root.getAbsolutePath()); //NON-NLS
}
@Override
public void actionPerformed(ActionEvent e) {
/*
* Skip monitoring if ingest is not running.
*/
final IngestManager manager = IngestManager.getInstance();
if (manager.isIngestRunning() == false) {
return;
}
logMemoryUsage();
if (!enoughDiskSpace()) {
/*
* Shut down ingest by cancelling all ingest jobs.
*/
manager.cancelAllIngestJobs(IngestJob.CancellationReason.OUT_OF_DISK_SPACE);
String diskPath = root.getAbsolutePath();
IngestServices.getInstance().postMessage(IngestMessage.createManagerErrorMessage(
NbBundle.getMessage(this.getClass(), "IngestMonitor.mgrErrMsg.lowDiskSpace.title", diskPath),
NbBundle.getMessage(this.getClass(), "IngestMonitor.mgrErrMsg.lowDiskSpace.msg", diskPath)));
MONITOR_LOGGER.log(Level.SEVERE, "Stopping ingest due to low disk space on {0}", diskPath); //NON-NLS
logger.log(Level.SEVERE, "Stopping ingest due to low disk space on {0}", diskPath); //NON-NLS
}
}
/**
* Writes current message usage to the memory usage log.
*/
private void logMemoryUsage() {
MONITOR_LOGGER.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
}
/**
* Determines whether there is enough disk space to continue running
* ingest.
*
* @return true if OK, false otherwise
*/
private boolean enoughDiskSpace() {
long freeSpace;
try {
freeSpace = getFreeSpace();
} catch (SecurityException e) {
logger.log(Level.WARNING, "Unable to check for free disk space (permission issue)", e); //NON-NLS
return true; //OK
}
if (freeSpace == DISK_FREE_SPACE_UNKNOWN) {
return true;
} else {
return freeSpace > MIN_FREE_DISK_SPACE;
}
}
/**
* Get free space in bytes of the drive where case dir resides, or -1 if
* unknown
*
* @return free space in bytes
*/
private long getFreeSpace() throws SecurityException {
final long freeSpace = root.getFreeSpace();
if (0 == freeSpace) {
/*
* Check for a network drive, some network filesystems always
* return zero.
*/
final String monitoredPath = root.getAbsolutePath();
if (monitoredPath.startsWith("\\\\") || monitoredPath.startsWith("//")) {
return DISK_FREE_SPACE_UNKNOWN;
}
}
return freeSpace;
}
}
}