mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge pull request #4684 from rcordovano/4915-case-node-data-fixes-and-improvements
4915 case node data fixes and improvements
This commit is contained in:
commit
f9c71b95fe
@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Services;
|
||||
import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction;
|
||||
@ -1620,11 +1621,10 @@ public class Case {
|
||||
}
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
|
||||
try {
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
|
||||
CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
|
||||
nodeData.setDisplayName(caseDetails.getCaseDisplayName());
|
||||
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException | IOException ex) {
|
||||
CaseNodeData.writeCaseNodeData(nodeData);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
}
|
||||
@ -2005,10 +2005,8 @@ public class Case {
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
|
||||
try {
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
CaseNodeData nodeData = new CaseNodeData(metadata);
|
||||
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) {
|
||||
CaseNodeData.createCaseNodeData(metadata);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
}
|
||||
@ -2033,27 +2031,10 @@ public class Case {
|
||||
if (getCaseType() == CaseType.MULTI_USER_CASE) {
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
|
||||
try {
|
||||
CaseNodeData nodeData;
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
byte[] nodeBytes = coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory());
|
||||
if (nodeBytes != null && nodeBytes.length > 0) {
|
||||
/*
|
||||
* Update the last access date in the coordination service
|
||||
* node data for the case.
|
||||
*/
|
||||
nodeData = new CaseNodeData(nodeBytes);
|
||||
nodeData.setLastAccessDate(new Date());
|
||||
} else {
|
||||
/*
|
||||
* This is a "legacy" case with no data stored in its case
|
||||
* directory coordination service node yet, or the node is
|
||||
* empty due to some error, so create the coordination
|
||||
* service node data from the case metadata.
|
||||
*/
|
||||
nodeData = new CaseNodeData(metadata);
|
||||
}
|
||||
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
|
||||
} catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) {
|
||||
CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
|
||||
nodeData.setLastAccessDate(new Date());
|
||||
CaseNodeData.writeCaseNodeData(nodeData);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
|
||||
}
|
||||
}
|
||||
@ -2633,9 +2614,8 @@ public class Case {
|
||||
|
||||
progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
|
||||
try {
|
||||
byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory());
|
||||
caseNodeData = new CaseNodeData(nodeBytes);
|
||||
} catch (CoordinationServiceException | InterruptedException | IOException ex) {
|
||||
caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
|
||||
throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
|
||||
}
|
||||
@ -2899,9 +2879,8 @@ public class Case {
|
||||
private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
|
||||
try {
|
||||
caseNodeData.setDeletedFlag(flag);
|
||||
CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray());
|
||||
} catch (IOException | CoordinationServiceException ex) {
|
||||
CaseNodeData.writeCaseNodeData(caseNodeData);
|
||||
} catch (CaseNodeDataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
|
||||
}
|
||||
}
|
||||
|
@ -22,25 +22,33 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* An object that converts data for a case directory lock coordination service
|
||||
* node to and from byte arrays.
|
||||
* Case data stored in a case directory coordination service node.
|
||||
*/
|
||||
public final class CaseNodeData {
|
||||
|
||||
private static final int CURRENT_VERSION = 1;
|
||||
private static final int MAJOR_VERSION = 2;
|
||||
private static final int MINOR_VERSION = 0;
|
||||
private static final Logger logger = Logger.getLogger(CaseNodeData.class.getName());
|
||||
|
||||
/*
|
||||
* Version 0 fields.
|
||||
* Version 0 fields. Note that version 0 node data was only written to the
|
||||
* coordination service node if an auto ingest job error occurred.
|
||||
*/
|
||||
private final int version;
|
||||
private int version;
|
||||
private boolean errorsOccurred;
|
||||
|
||||
/*
|
||||
@ -53,28 +61,191 @@ public final class CaseNodeData {
|
||||
private String displayName;
|
||||
private short deletedItemFlags;
|
||||
|
||||
/**
|
||||
* Gets the current version of the case directory lock coordination service
|
||||
* node data.
|
||||
*
|
||||
* @return The version number.
|
||||
/*
|
||||
* Version 2 fields.
|
||||
*/
|
||||
public static int getCurrentVersion() {
|
||||
return CaseNodeData.CURRENT_VERSION;
|
||||
private int minorVersion;
|
||||
|
||||
/**
|
||||
* Creates case node data from the metadata for a case and writes it to the
|
||||
* appropriate case directory coordination service node, which must already
|
||||
* exist.
|
||||
*
|
||||
* @param metadata The case metadata.
|
||||
*
|
||||
* @return The case node data that was written to the coordination service
|
||||
* node.
|
||||
*
|
||||
* @throws CaseNodeDataException If there is an error creating or writing
|
||||
* the case node data.
|
||||
* @throws InterruptedException If the current thread is interrupted while
|
||||
* waiting for the coordination service.
|
||||
*/
|
||||
public static CaseNodeData createCaseNodeData(final CaseMetadata metadata) throws CaseNodeDataException, InterruptedException {
|
||||
try {
|
||||
final CaseNodeData nodeData = new CaseNodeData(metadata);
|
||||
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeData.getDirectory().toString(), nodeData.toArray());
|
||||
return nodeData;
|
||||
|
||||
} catch (ParseException | IOException | CoordinationServiceException ex) {
|
||||
throw new CaseNodeDataException(String.format("Error creating case node data for coordination service node with path %s", metadata.getCaseDirectory().toUpperCase()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a CaseMetadata object to construct an object that converts data for
|
||||
* a case directory lock coordination service node to and from byte arrays.
|
||||
* Reads case data from a case directory coordination service node. If the
|
||||
* data is missing, corrupted, or from an older version of the software, an
|
||||
* attempt is made to remedy the situation using the case metadata.
|
||||
*
|
||||
* @param nodePath The case directory coordination service node path.
|
||||
*
|
||||
* @return The case node data.
|
||||
*
|
||||
* @throws CaseNodeDataException If there is an error reading or writing the
|
||||
* case node data.
|
||||
* @throws InterruptedException If the current thread is interrupted while
|
||||
* waiting for the coordination service.
|
||||
*/
|
||||
public static CaseNodeData readCaseNodeData(String nodePath) throws CaseNodeDataException, InterruptedException {
|
||||
try {
|
||||
CaseNodeData nodeData;
|
||||
final byte[] nodeBytes = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, nodePath);
|
||||
if (nodeBytes != null && nodeBytes.length > 0) {
|
||||
try {
|
||||
nodeData = new CaseNodeData(nodeBytes);
|
||||
} catch (IOException ex) {
|
||||
/*
|
||||
* The existing case node data is corrupted.
|
||||
*/
|
||||
logger.log(Level.WARNING, String.format("Error reading node data for coordination service node with path %s, will attempt to replace it", nodePath.toUpperCase()), ex); //NON-NLS
|
||||
final CaseMetadata metadata = getCaseMetadata(nodePath);
|
||||
nodeData = createCaseNodeData(metadata);
|
||||
logger.log(Level.INFO, String.format("Replaced corrupt node data for coordination service node with path %s", nodePath.toUpperCase())); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* The case node data is missing. Version 0 node data was only
|
||||
* written to the coordination service node if an auto ingest
|
||||
* job error occurred.
|
||||
*/
|
||||
logger.log(Level.INFO, String.format("Missing node data for coordination service node with path %s, will attempt to create it", nodePath.toUpperCase())); //NON-NLS
|
||||
final CaseMetadata metadata = getCaseMetadata(nodePath);
|
||||
nodeData = createCaseNodeData(metadata);
|
||||
logger.log(Level.INFO, String.format("Created node data for coordination service node with path %s", nodePath.toUpperCase())); //NON-NLS
|
||||
}
|
||||
if (nodeData.getVersion() < CaseNodeData.MAJOR_VERSION) {
|
||||
nodeData = upgradeCaseNodeData(nodePath, nodeData);
|
||||
}
|
||||
return nodeData;
|
||||
|
||||
} catch (CaseNodeDataException | CaseMetadataException | ParseException | IOException | CoordinationServiceException ex) {
|
||||
throw new CaseNodeDataException(String.format("Error reading/writing node data coordination service node with path %s", nodePath.toUpperCase()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes case data to a case directory coordination service node. Obtain
|
||||
* the case data to be updated and written by calling createCaseNodeData()
|
||||
* or readCaseNodeData().
|
||||
*
|
||||
* @param nodeData The case node data.
|
||||
*
|
||||
* @throws CaseNodeDataException If there is an error writing the case node
|
||||
* data.
|
||||
* @throws InterruptedException If the current thread is interrupted while
|
||||
* waiting for the coordination service.
|
||||
*/
|
||||
public static void writeCaseNodeData(CaseNodeData nodeData) throws CaseNodeDataException, InterruptedException {
|
||||
try {
|
||||
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeData.getDirectory().toString(), nodeData.toArray());
|
||||
|
||||
} catch (IOException | CoordinationServiceException ex) {
|
||||
throw new CaseNodeDataException(String.format("Error writing node data coordination service node with path %s", nodeData.getDirectory().toString().toUpperCase()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades older versions of node data to the current version and writes
|
||||
* the data back to the case directory coordination service node.
|
||||
*
|
||||
* @param nodePath The case directory coordination service node path.
|
||||
* @param oldNodeData The outdated node data.
|
||||
*
|
||||
* @return The updated node data.
|
||||
*
|
||||
* @throws CaseNodeDataException If the case meta data file or case
|
||||
* directory do not exist.
|
||||
* @throws CaseMetadataException If the case metadata cannot be read.
|
||||
*/
|
||||
private static CaseNodeData upgradeCaseNodeData(String nodePath, CaseNodeData oldNodeData) throws CaseNodeDataException, CaseMetadataException, ParseException, IOException, CoordinationServiceException, InterruptedException {
|
||||
CaseNodeData nodeData;
|
||||
switch (oldNodeData.getVersion()) {
|
||||
case 0:
|
||||
/*
|
||||
* Version 0 node data consisted of only the version number and
|
||||
* the errors occurred flag and was only written when an auto
|
||||
* ingest job error occurred. To upgrade from version 0, the
|
||||
* version 1 fields need to be set from the case metadata and
|
||||
* the errors occurred flag needs to be carried forward. Note
|
||||
* that the last accessed date gets advanced to now, since it is
|
||||
* otherwise unknown.
|
||||
*/
|
||||
final CaseMetadata metadata = getCaseMetadata(nodePath);
|
||||
nodeData = new CaseNodeData(metadata);
|
||||
nodeData.setErrorsOccurred(oldNodeData.getErrorsOccurred());
|
||||
break;
|
||||
case 1:
|
||||
/*
|
||||
* Version 1 node data did not have a minor version number
|
||||
* field.
|
||||
*/
|
||||
oldNodeData.setMinorVersion(MINOR_VERSION);
|
||||
nodeData = oldNodeData;
|
||||
break;
|
||||
default:
|
||||
nodeData = oldNodeData;
|
||||
break;
|
||||
}
|
||||
writeCaseNodeData(nodeData);
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the metadata for a case.
|
||||
*
|
||||
* @param nodePath The case directory coordination service node path for the
|
||||
* case.
|
||||
*
|
||||
* @return The case metadata.
|
||||
*
|
||||
* @throws CaseNodeDataException If the case metadata file or the case
|
||||
* directory does not exist.
|
||||
* @throws CaseMetadataException If the case metadata cannot be read.
|
||||
*/
|
||||
private static CaseMetadata getCaseMetadata(String nodePath) throws CaseNodeDataException, CaseMetadataException {
|
||||
final Path caseDirectoryPath = Paths.get(nodePath);
|
||||
final File caseDirectory = caseDirectoryPath.toFile();
|
||||
if (!caseDirectory.exists()) {
|
||||
throw new CaseNodeDataException("Case directory does not exist"); // NON-NLS
|
||||
}
|
||||
final Path metadataFilePath = CaseMetadata.getCaseMetadataFilePath(caseDirectoryPath);
|
||||
if (metadataFilePath == null) {
|
||||
throw new CaseNodeDataException("Case meta data file does not exist"); // NON-NLS
|
||||
}
|
||||
return new CaseMetadata(metadataFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses case metadata to construct the case data to store in a case
|
||||
* directory coordination service node.
|
||||
*
|
||||
* @param metadata The case meta data.
|
||||
*
|
||||
* @throws java.text.ParseException If there is an error parsing dates from
|
||||
* string representations of dates in the
|
||||
* meta data.
|
||||
* @throws ParseException If there is an error parsing dates from string
|
||||
* representations of dates in the meta data.
|
||||
*/
|
||||
public CaseNodeData(CaseMetadata metadata) throws ParseException {
|
||||
this.version = CURRENT_VERSION;
|
||||
private CaseNodeData(CaseMetadata metadata) throws ParseException {
|
||||
this.version = MAJOR_VERSION;
|
||||
this.errorsOccurred = false;
|
||||
this.directory = Paths.get(metadata.getCaseDirectory());
|
||||
this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate());
|
||||
@ -82,51 +253,64 @@ public final class CaseNodeData {
|
||||
this.name = metadata.getCaseName();
|
||||
this.displayName = metadata.getCaseDisplayName();
|
||||
this.deletedItemFlags = 0;
|
||||
this.minorVersion = MINOR_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses coordination service node data to construct an object that converts
|
||||
* data for a case directory lock coordination service node to and from byte
|
||||
* arrays.
|
||||
* Uses the raw bytes from a case directory coordination service node to
|
||||
* construct a case node data object.
|
||||
*
|
||||
* @param nodeData The raw bytes received from the coordination service.
|
||||
*
|
||||
* @throws IOException If there is an error reading the node data.
|
||||
*/
|
||||
public CaseNodeData(byte[] nodeData) throws IOException {
|
||||
private CaseNodeData(byte[] nodeData) throws IOException {
|
||||
if (nodeData == null || nodeData.length == 0) {
|
||||
throw new IOException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array");
|
||||
}
|
||||
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(nodeData));
|
||||
this.version = inputStream.readInt();
|
||||
if (this.version > 0) {
|
||||
this.errorsOccurred = inputStream.readBoolean();
|
||||
} else {
|
||||
short legacyErrorsOccurred = inputStream.readByte();
|
||||
this.errorsOccurred = (legacyErrorsOccurred < 0);
|
||||
}
|
||||
if (this.version > 0) {
|
||||
this.directory = Paths.get(inputStream.readUTF());
|
||||
this.createDate = new Date(inputStream.readLong());
|
||||
this.lastAccessDate = new Date(inputStream.readLong());
|
||||
this.name = inputStream.readUTF();
|
||||
this.displayName = inputStream.readUTF();
|
||||
this.deletedItemFlags = inputStream.readShort();
|
||||
try (ByteArrayInputStream byteStream = new ByteArrayInputStream(nodeData); DataInputStream inputStream = new DataInputStream(byteStream)) {
|
||||
this.version = inputStream.readInt();
|
||||
if (this.version == 1) {
|
||||
this.errorsOccurred = inputStream.readBoolean();
|
||||
} else {
|
||||
byte errorsOccurredByte = inputStream.readByte();
|
||||
this.errorsOccurred = (errorsOccurredByte < 0);
|
||||
}
|
||||
if (this.version > 0) {
|
||||
this.directory = Paths.get(inputStream.readUTF());
|
||||
this.createDate = new Date(inputStream.readLong());
|
||||
this.lastAccessDate = new Date(inputStream.readLong());
|
||||
this.name = inputStream.readUTF();
|
||||
this.displayName = inputStream.readUTF();
|
||||
this.deletedItemFlags = inputStream.readShort();
|
||||
}
|
||||
if (this.version > 1) {
|
||||
this.minorVersion = inputStream.readInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node data version number of this node.
|
||||
* Gets the version number of this node data.
|
||||
*
|
||||
* @return The version number.
|
||||
*/
|
||||
public int getVersion() {
|
||||
private int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minor version number of this node data.
|
||||
*
|
||||
* @param version The version number.
|
||||
*/
|
||||
private void setMinorVersion(int minorVersion) {
|
||||
this.minorVersion = minorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether or not any errors occurred during the processing of any auto
|
||||
* ingest job for the case represented by this node data.
|
||||
* ingest job for the case.
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
@ -136,7 +320,7 @@ public final class CaseNodeData {
|
||||
|
||||
/**
|
||||
* Sets whether or not any errors occurred during the processing of any auto
|
||||
* ingest job for the case represented by this node data.
|
||||
* ingest job for the case.
|
||||
*
|
||||
* @param errorsOccurred True or false.
|
||||
*/
|
||||
@ -145,8 +329,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the case directory of the case represented by this node
|
||||
* data.
|
||||
* Gets the path of the case directory.
|
||||
*
|
||||
* @return The case directory path.
|
||||
*/
|
||||
@ -155,17 +338,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path of the case directory of the case represented by this node
|
||||
* data.
|
||||
*
|
||||
* @param caseDirectory The case directory path.
|
||||
*/
|
||||
public void setDirectory(Path caseDirectory) {
|
||||
this.directory = caseDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date the case represented by this node data was created.
|
||||
* Gets the date the case was created.
|
||||
*
|
||||
* @return The create date.
|
||||
*/
|
||||
@ -174,16 +347,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date the case represented by this node data was created.
|
||||
*
|
||||
* @param createDate The create date.
|
||||
*/
|
||||
public void setCreateDate(Date createDate) {
|
||||
this.createDate = new Date(createDate.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date the case represented by this node data last accessed.
|
||||
* Gets the date the case was last accessed.
|
||||
*
|
||||
* @return The last access date.
|
||||
*/
|
||||
@ -192,7 +356,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date the case represented by this node data was last accessed.
|
||||
* Sets the date the case was last accessed.
|
||||
*
|
||||
* @param lastAccessDate The last access date.
|
||||
*/
|
||||
@ -201,8 +365,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique and immutable (user cannot change it) name of the case
|
||||
* represented by this node data.
|
||||
* Gets the unique and immutable name of the case.
|
||||
*
|
||||
* @return The case name.
|
||||
*/
|
||||
@ -211,17 +374,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unique and immutable (user cannot change it) name of the case
|
||||
* represented by this node data.
|
||||
*
|
||||
* @param name The case name.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display name of the case represented by this node data.
|
||||
* Gets the display name of the case.
|
||||
*
|
||||
* @return The case display name.
|
||||
*/
|
||||
@ -230,7 +383,7 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name of the case represented by this node data.
|
||||
* Sets the display name of the case.
|
||||
*
|
||||
* @param displayName The case display name.
|
||||
*/
|
||||
@ -239,19 +392,18 @@ public final class CaseNodeData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a deleted item flag is set for the case represented by
|
||||
* this node data.
|
||||
* Checks whether a given deleted item flag is set for the case.
|
||||
*
|
||||
* @param flag The flag to check.
|
||||
*
|
||||
* @return
|
||||
* @return True or false.
|
||||
*/
|
||||
public boolean isDeletedFlagSet(DeletedFlags flag) {
|
||||
return (this.deletedItemFlags & flag.getValue()) == flag.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a deleted item flag for the case represented by this node data.
|
||||
* Sets a given deleted item flag.
|
||||
*
|
||||
* @param flag The flag to set.
|
||||
*/
|
||||
@ -265,22 +417,24 @@ public final class CaseNodeData {
|
||||
*
|
||||
* @return The node data as a byte array.
|
||||
*
|
||||
* @throws IOException If there is an error writing the node data.
|
||||
* @throws IOException If there is an error writing the node data to the
|
||||
* array.
|
||||
*/
|
||||
public byte[] toArray() throws IOException {
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
DataOutputStream outputStream = new DataOutputStream(byteStream);
|
||||
outputStream.writeInt(this.version);
|
||||
outputStream.writeBoolean(this.errorsOccurred);
|
||||
outputStream.writeUTF(this.directory.toString());
|
||||
outputStream.writeLong(this.createDate.getTime());
|
||||
outputStream.writeLong(this.lastAccessDate.getTime());
|
||||
outputStream.writeUTF(this.name);
|
||||
outputStream.writeUTF(this.displayName);
|
||||
outputStream.writeShort(this.deletedItemFlags);
|
||||
outputStream.flush();
|
||||
byteStream.flush();
|
||||
return byteStream.toByteArray();
|
||||
private byte[] toArray() throws IOException {
|
||||
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(byteStream)) {
|
||||
outputStream.writeInt(this.version);
|
||||
outputStream.writeByte((byte) (this.errorsOccurred ? 0x80 : 0));
|
||||
outputStream.writeUTF(this.directory.toString());
|
||||
outputStream.writeLong(this.createDate.getTime());
|
||||
outputStream.writeLong(this.lastAccessDate.getTime());
|
||||
outputStream.writeUTF(this.name);
|
||||
outputStream.writeUTF(this.displayName);
|
||||
outputStream.writeShort(this.deletedItemFlags);
|
||||
outputStream.writeInt(this.minorVersion);
|
||||
outputStream.flush();
|
||||
byteStream.flush();
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -316,4 +470,34 @@ public final class CaseNodeData {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when there is an error reading or writing case node
|
||||
* data.
|
||||
*/
|
||||
public static final class CaseNodeDataException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an exception to throw when there is an error reading or
|
||||
* writing case node data.
|
||||
*
|
||||
* @param message The exception message.
|
||||
*/
|
||||
private CaseNodeDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception to throw when there is an error reading or
|
||||
* writing case node data.
|
||||
*
|
||||
* @param message The exception message.
|
||||
* @param cause The cause of the exception.
|
||||
*/
|
||||
private CaseNodeDataException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,31 +18,28 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule.multiusercases;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseAutoIngestLogNodePath;
|
||||
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseNameNodePath;
|
||||
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseResourcesNodePath;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* Queries the coordination service to collect the multi-user case node data
|
||||
* stored in the case directory lock ZooKeeper nodes.
|
||||
* Collects the multi-user case node data stored in the case directory
|
||||
* coordination service nodes.
|
||||
*/
|
||||
final public class CaseNodeDataCollector {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CaseNodeDataCollector.class.getName());
|
||||
|
||||
/**
|
||||
* Queries the coordination service to collect the multi-user case node data
|
||||
* stored in the case directory lock ZooKeeper nodes.
|
||||
* Collects the multi-user case node data stored in the case directory
|
||||
* coordination service nodes.
|
||||
*
|
||||
* @return The node data for the multi-user cases known to the coordination
|
||||
* service.
|
||||
@ -54,128 +51,30 @@ final public class CaseNodeDataCollector {
|
||||
* service.
|
||||
*/
|
||||
public static List<CaseNodeData> getNodeData() throws CoordinationServiceException, InterruptedException {
|
||||
final List<CaseNodeData> cases = new ArrayList<>();
|
||||
final List<CaseNodeData> nodeDataList = new ArrayList<>();
|
||||
final CoordinationService coordinationService = CoordinationService.getInstance();
|
||||
final List<String> nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
|
||||
for (String nodeName : nodeList) {
|
||||
if (CoordinationServiceUtils.isCaseNameNodePath(nodeName)
|
||||
|| CoordinationServiceUtils.isCaseResourcesNodePath(nodeName)
|
||||
|| CoordinationServiceUtils.isCaseAutoIngestLogNodePath(nodeName)) {
|
||||
final List<String> nodePaths = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
|
||||
for (String nodePath : nodePaths) {
|
||||
/*
|
||||
* Skip the case name, case resources, and case auto ingest log
|
||||
* coordination service nodes. They are not used to store case data.
|
||||
*/
|
||||
if (isCaseNameNodePath(nodePath) || isCaseResourcesNodePath(nodePath) || isCaseAutoIngestLogNodePath(nodePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the data from the case directory lock node. This data may not
|
||||
* exist or may exist only in an older version. If it is missing or
|
||||
* incomplete, create or update it.
|
||||
* Get the case node data from the case directory coordination service node.
|
||||
*/
|
||||
try {
|
||||
CaseNodeData nodeData;
|
||||
final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName);
|
||||
if (nodeBytes != null && nodeBytes.length > 0) {
|
||||
nodeData = new CaseNodeData(nodeBytes);
|
||||
if (nodeData.getVersion() < CaseNodeData.getCurrentVersion()) {
|
||||
nodeData = updateNodeData(nodeName, nodeData);
|
||||
}
|
||||
} else {
|
||||
nodeData = updateNodeData(nodeName, null);
|
||||
}
|
||||
if (nodeData != null) {
|
||||
cases.add(nodeData);
|
||||
}
|
||||
|
||||
} catch (CoordinationService.CoordinationServiceException | InterruptedException | IOException | ParseException | CaseMetadata.CaseMetadataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex);
|
||||
final CaseNodeData nodeData = CaseNodeData.readCaseNodeData(nodePath);
|
||||
nodeDataList.add(nodeData);
|
||||
} catch (CaseNodeDataException | InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error reading case node data from %s", nodePath), ex);
|
||||
}
|
||||
|
||||
}
|
||||
return cases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the case directory lock coordination service node data for a
|
||||
* case.
|
||||
*
|
||||
* @param nodeName The coordination service node name, i.e., the case
|
||||
* directory path.
|
||||
* @param oldNodeData The node data to be updated.
|
||||
*
|
||||
* @return A CaseNodedata object or null if the coordination service node is
|
||||
* an "orphan" with no corresponding case directry.
|
||||
*
|
||||
* @throws IOException If there is an error writing the
|
||||
* node data to a byte array.
|
||||
* @throws CaseMetadataException If there is an error reading the
|
||||
* case metadata file.
|
||||
* @throws ParseException If there is an error parsing a date
|
||||
* from the case metadata file.
|
||||
* @throws CoordinationServiceException If there is an error interacting
|
||||
* with the coordination service.
|
||||
* @throws InterruptedException If a coordination service operation
|
||||
* is interrupted.
|
||||
*/
|
||||
private static CaseNodeData updateNodeData(String nodeName, CaseNodeData oldNodeData) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException {
|
||||
Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS);
|
||||
File caseDirectory = caseDirectoryPath.toFile();
|
||||
if (!caseDirectory.exists()) {
|
||||
logger.log(Level.WARNING, String.format("Found orphan coordination service node %s, attempting clean up", caseDirectoryPath));
|
||||
deleteLockNodes(CoordinationService.getInstance(), caseDirectoryPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
CaseNodeData nodeData = null;
|
||||
if (oldNodeData == null || oldNodeData.getVersion() == 0) {
|
||||
File[] files = caseDirectory.listFiles();
|
||||
for (File file : files) {
|
||||
String name = file.getName().toLowerCase();
|
||||
if (name.endsWith(CaseMetadata.getFileExtension())) {
|
||||
CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath()));
|
||||
nodeData = new CaseNodeData(metadata);
|
||||
if (oldNodeData != null) {
|
||||
/*
|
||||
* Version 0 case node data was only written if errors
|
||||
* occurred during an auto ingest job.
|
||||
*/
|
||||
nodeData.setErrorsOccurred(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeData != null) {
|
||||
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray());
|
||||
}
|
||||
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete the coordination service lock nodes for a case,
|
||||
* logging any failures.
|
||||
*
|
||||
* @param coordinationService The coordination service.
|
||||
* @param caseDirectoryPath The case directory path.
|
||||
*/
|
||||
private static void deleteLockNodes(CoordinationService coordinationService, Path caseDirectoryPath) {
|
||||
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseResourcesNodePath(caseDirectoryPath));
|
||||
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseDirectoryPath));
|
||||
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseDirectoryNodePath(caseDirectoryPath));
|
||||
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseNameNodePath(caseDirectoryPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete a coordination service node, logging failure.
|
||||
*
|
||||
* @param coordinationService The coordination service.
|
||||
* @param nodeName A node name.
|
||||
*/
|
||||
private static void deleteCoordinationServiceNode(CoordinationService coordinationService, String nodeName) {
|
||||
try {
|
||||
coordinationService.deleteNode(CoordinationService.CategoryNode.CASES, nodeName);
|
||||
} catch (CoordinationService.CoordinationServiceException | InterruptedException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error deleting coordination service node %s", nodeName), ex);
|
||||
}
|
||||
return nodeDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,5 +82,5 @@ final public class CaseNodeDataCollector {
|
||||
*/
|
||||
private CaseNodeDataCollector() {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseDetails;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock;
|
||||
@ -1016,22 +1017,19 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
|
||||
*
|
||||
* @param caseDirectoryPath The case directory path.
|
||||
*
|
||||
* @throws CoordinationServiceException If there was an error getting the
|
||||
* node data from the cooordination
|
||||
* service.
|
||||
* @throws IOException If the node data was missing or
|
||||
* there was an error interpreting it.
|
||||
* @throws InterruptedException If the thread running the input
|
||||
* directory scan task is interrupted
|
||||
* while blocked, i.e., if auto ingest
|
||||
* is shutting down.
|
||||
* @throws InterruptedException If the thread running the input directory
|
||||
* scan task is interrupted while blocked,
|
||||
* i.e., if auto ingest is shutting down.
|
||||
*/
|
||||
private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws IOException, CoordinationServiceException, InterruptedException {
|
||||
byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString());
|
||||
CaseNodeData caseNodeData = new CaseNodeData(rawData);
|
||||
caseNodeData.setErrorsOccurred(true);
|
||||
rawData = caseNodeData.toArray();
|
||||
coordinationService.setNodeData(CoordinationService.CategoryNode.CASES, caseDirectoryPath.toString(), rawData);
|
||||
private void setCaseNodeDataErrorsOccurred(Path caseDirectoryPath) throws InterruptedException {
|
||||
try {
|
||||
CaseNodeData caseNodeData = CaseNodeData.readCaseNodeData(caseDirectoryPath.toString());
|
||||
caseNodeData.setErrorsOccurred(true);
|
||||
CaseNodeData.writeCaseNodeData(caseNodeData);
|
||||
} catch (CaseNodeDataException ex) {
|
||||
sysLogger.log(Level.WARNING, String.format("Error attempting to set error flag in case node data for %s", caseDirectoryPath), ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1381,11 +1379,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
|
||||
if (null != caseDirectoryPath) {
|
||||
job.setCaseDirectoryPath(caseDirectoryPath);
|
||||
job.setErrorsOccurred(true);
|
||||
try {
|
||||
setCaseNodeDataErrorsOccurred(caseDirectoryPath);
|
||||
} catch (IOException ex) {
|
||||
sysLogger.log(Level.WARNING, String.format("Error attempting to set error flag in case node data for %s", caseDirectoryPath), ex);
|
||||
}
|
||||
setCaseNodeDataErrorsOccurred(caseDirectoryPath);
|
||||
} else {
|
||||
job.setErrorsOccurred(false);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
|
||||
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
|
||||
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
|
||||
@ -785,12 +786,14 @@ final class DeleteCaseTask implements Runnable {
|
||||
* case.
|
||||
*
|
||||
* @param flag The flag to set.
|
||||
*
|
||||
* @throws InterruptedException If the interrupted flag is set.
|
||||
*/
|
||||
private void setDeletedItemFlag(CaseNodeData.DeletedFlags flag) {
|
||||
private void setDeletedItemFlag(CaseNodeData.DeletedFlags flag) throws InterruptedException {
|
||||
try {
|
||||
caseNodeData.setDeletedFlag(flag);
|
||||
coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray());
|
||||
} catch (IOException | CoordinationServiceException | InterruptedException ex) {
|
||||
CaseNodeData.writeCaseNodeData(caseNodeData);
|
||||
} catch (CaseNodeDataException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s", flag.name(), caseNodeData.getDisplayName()), ex);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user