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:
Richard Cordovano 2019-04-08 12:32:44 -04:00 committed by GitHub
commit f9c71b95fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 340 additions and 281 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
/**

View File

@ -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);
}

View File

@ -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);
}
}