From a7cc1ce09e0e0b7a6c76b03b6186511e318d6fd7 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Jan 2017 11:26:06 -0500 Subject: [PATCH 1/3] Moved CoordinationService from Experimental to Core --- Core/nbproject/project.properties | 5 +++++ Core/nbproject/project.xml | 17 +++++++++++++++++ .../modules/ext/curator-client-2.8.0.jar | Bin .../modules/ext/curator-framework-2.8.0.jar | Bin .../ext/curator-recipes-2.8.0-sources.jar | Bin .../modules/ext/curator-recipes-2.8.0.jar | Bin .../release/modules/ext/zookeeper-3.4.6.jar | Bin .../CoordinationService.java | 2 +- Experimental/nbproject/project.properties | 5 ----- Experimental/nbproject/project.xml | 16 ---------------- .../autoingest/AutoIngestJobLogger.java | 6 +++--- .../autoingest/AutoIngestManager.java | 6 +++--- .../autoingest/ReviewModeCaseManager.java | 4 ++-- .../configuration/SharedConfiguration.java | 6 +++--- 14 files changed, 34 insertions(+), 33 deletions(-) rename {Experimental => Core}/release/modules/ext/curator-client-2.8.0.jar (100%) rename {Experimental => Core}/release/modules/ext/curator-framework-2.8.0.jar (100%) rename {Experimental => Core}/release/modules/ext/curator-recipes-2.8.0-sources.jar (100%) rename {Experimental => Core}/release/modules/ext/curator-recipes-2.8.0.jar (100%) rename {Experimental => Core}/release/modules/ext/zookeeper-3.4.6.jar (100%) rename {Experimental/src/org/sleuthkit/autopsy/experimental => Core/src/org/sleuthkit/autopsy}/coordinationservice/CoordinationService.java (99%) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index c3a40f5a77..e3895cc49d 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -15,6 +15,10 @@ file.reference.StixLib.jar=release/modules/ext/StixLib.jar file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar file.reference.Tsk_DataModel_PostgreSQL.jar=release/modules/ext/Tsk_DataModel_PostgreSQL.jar file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar +file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar +file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar +file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar +file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial javadoc.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip @@ -23,5 +27,6 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip!/Source/ +source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar spec.version.base=10.7 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 99ae49a9df..28a8815bca 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -231,6 +231,7 @@ org.sleuthkit.autopsy.casemodule.events org.sleuthkit.autopsy.casemodule.services org.sleuthkit.autopsy.contentviewers + org.sleuthkit.autopsy.coordinationservice org.sleuthkit.autopsy.core org.sleuthkit.autopsy.core.events org.sleuthkit.autopsy.corecomponentinterfaces @@ -326,6 +327,22 @@ ext/Tsk_DataModel_PostgreSQL.jar release/modules/ext/Tsk_DataModel_PostgreSQL.jar + + ext/zookeeper-3.4.6.jar + release/modules/ext/zookeeper-3.4.6.jar + + + ext/curator-client-2.8.0.jar + release/modules/ext/curator-client-2.8.0.jar + + + ext/curator-recipes-2.8.0.jar + release/modules/ext/curator-recipes-2.8.0.jar + + + ext/curator-framework-2.8.0.jar + release/modules/ext/curator-framework-2.8.0.jar + diff --git a/Experimental/release/modules/ext/curator-client-2.8.0.jar b/Core/release/modules/ext/curator-client-2.8.0.jar similarity index 100% rename from Experimental/release/modules/ext/curator-client-2.8.0.jar rename to Core/release/modules/ext/curator-client-2.8.0.jar diff --git a/Experimental/release/modules/ext/curator-framework-2.8.0.jar b/Core/release/modules/ext/curator-framework-2.8.0.jar similarity index 100% rename from Experimental/release/modules/ext/curator-framework-2.8.0.jar rename to Core/release/modules/ext/curator-framework-2.8.0.jar diff --git a/Experimental/release/modules/ext/curator-recipes-2.8.0-sources.jar b/Core/release/modules/ext/curator-recipes-2.8.0-sources.jar similarity index 100% rename from Experimental/release/modules/ext/curator-recipes-2.8.0-sources.jar rename to Core/release/modules/ext/curator-recipes-2.8.0-sources.jar diff --git a/Experimental/release/modules/ext/curator-recipes-2.8.0.jar b/Core/release/modules/ext/curator-recipes-2.8.0.jar similarity index 100% rename from Experimental/release/modules/ext/curator-recipes-2.8.0.jar rename to Core/release/modules/ext/curator-recipes-2.8.0.jar diff --git a/Experimental/release/modules/ext/zookeeper-3.4.6.jar b/Core/release/modules/ext/zookeeper-3.4.6.jar similarity index 100% rename from Experimental/release/modules/ext/zookeeper-3.4.6.jar rename to Core/release/modules/ext/zookeeper-3.4.6.jar diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java similarity index 99% rename from Experimental/src/org/sleuthkit/autopsy/experimental/coordinationservice/CoordinationService.java rename to Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index 8b0b293165..a433d17cfd 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.experimental.coordinationservice; +package org.sleuthkit.autopsy.coordinationservice; import java.util.HashMap; import java.util.Map; diff --git a/Experimental/nbproject/project.properties b/Experimental/nbproject/project.properties index 140b9f1b5c..49be6268a5 100644 --- a/Experimental/nbproject/project.properties +++ b/Experimental/nbproject/project.properties @@ -1,17 +1,12 @@ file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar -file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar -file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar -file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar file.reference.jackson-core-2.7.0.jar=release/modules/ext/jackson-core-2.7.0.jar file.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1.jar file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1.jar file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar -file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial javadoc.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1-javadoc.jar javadoc.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1-javadoc.jar -source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar source.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1-sources.jar source.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1-sources.jar diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index 8ab88c13b5..63cedca0de 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -119,14 +119,6 @@ org.sleuthkit.autopsy.experimental.autoingest org.sleuthkit.autopsy.experimental.configuration - - ext/zookeeper-3.4.6.jar - release/modules/ext/zookeeper-3.4.6.jar - - - ext/curator-client-2.8.0.jar - release/modules/ext/curator-client-2.8.0.jar - ext/mchange-commons-java-0.2.9.jar release/modules/ext/mchange-commons-java-0.2.9.jar @@ -139,10 +131,6 @@ ext/tika-core-1.5.jar release/modules/ext/tika-core-1.5.jar - - ext/curator-recipes-2.8.0.jar - release/modules/ext/curator-recipes-2.8.0.jar - ext/jackson-core-2.7.0.jar release/modules/ext/jackson-core-2.7.0.jar @@ -151,10 +139,6 @@ ext/c3p0-0.9.5.jar release/modules/ext/c3p0-0.9.5.jar - - ext/curator-framework-2.8.0.jar - release/modules/ext/curator-framework-2.8.0.jar - ext/solr-solrj-4.9.1.jar release/modules/ext/solr-solrj-4.9.1.jar diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index f3828d5594..c56bec23b5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -29,9 +29,9 @@ import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Date; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import java.util.concurrent.TimeUnit; import java.util.List; import javax.annotation.concurrent.Immutable; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index e213633e07..5c121d55c3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -98,9 +98,9 @@ import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.openide.util.Lookup; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java index 099ef36088..400f297476 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java @@ -37,8 +37,8 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseNewAction; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; /** * Handles opening, locking, and unlocking cases in review mode. Instances of diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 0e6d38c903..7599ed4648 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -49,9 +49,9 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestSettingsPanel.UpdateConfigSwingWorker; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; /* * A utility class for loading and saving shared configuration data From 0dc1775fffd78155cd07e425567316d70c2acc4b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 11 Jan 2017 14:58:46 -0500 Subject: [PATCH 2/3] Finished moving locks into Case --- .../autopsy/casemodule/Bundle.properties | 1 + .../sleuthkit/autopsy/casemodule/Case.java | 252 +++++++++++++++--- .../CoordinationService.java | 3 +- .../CoordinationServiceNamespace.java | 6 +- .../autoingest/AutoIngestJobLogger.java | 1 + .../autoingest/AutoIngestManager.java | 1 + .../autoingest/ReviewModeCaseManager.java | 83 +----- 7 files changed, 224 insertions(+), 123 deletions(-) rename {Experimental/src/org/sleuthkit/autopsy/experimental/autoingest => Core/src/org/sleuthkit/autopsy/coordinationservice}/CoordinationServiceNamespace.java (86%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index cbf9804b48..12184925e9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -133,6 +133,7 @@ Case.GetCaseTypeGivenPath.Failure=Unable to get case type Case.metaDataFileCorrupt.exception.msg=The case metadata file (.aut) is corrupted. Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk. Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1} +Case.exception.errorLocking=Unable to open case being updated by another user. CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ Case Name\: {0}\n\ Case Directory\: {1} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 0b804e1e3b..d681e81b48 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -39,6 +39,12 @@ import java.util.UUID; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.util.NbBundle; @@ -55,6 +61,8 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; @@ -280,6 +288,8 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); private static String appName; private static Case currentCase; + private static CoordinationService.Lock currentCaseLock; + private static ExecutorService currentCaseExecutor; private final CaseMetadata caseMetadata; private final SleuthkitCase db; private final Services services; @@ -832,10 +842,30 @@ public class Case implements SleuthkitCase.ErrorObserver { * exception. */ public void closeCase() throws CaseActionException { + + // The unlock must happen on the same thread that created the lock + Future future = getCurrentCaseExecutor().submit(() -> { + try{ + if (currentCaseLock != null) { + currentCaseLock.release(); + currentCaseLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing shared lock"), exx); + } + return null; + }); + try{ + future.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); + } + changeCurrentCase(null); + try { services.close(); - this.db.close(); + this.db.close(); } catch (Exception e) { throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.closeCase.exception.msg"), e); } @@ -940,6 +970,9 @@ public class Case implements SleuthkitCase.ErrorObserver { public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { logger.log(Level.INFO, "Attempting to create case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + CoordinationService.Lock exclusiveResourceLock = null; + boolean caseCreatedSuccessfully = false; + /* * Create case directory if it doesn't already exist. */ @@ -962,49 +995,113 @@ public class Case implements SleuthkitCase.ErrorObserver { } else if (caseType == CaseType.MULTI_USER_CASE) { dbName = indexName; } + + try{ + /* If this is a multi-user case, acquire two locks: + * - a shared lock on the case to prevent it from being deleted while open + * - an exclusive lock to prevent multiple clients from executing openCase simultaneously + */ + if(caseType == CaseType.MULTI_USER_CASE){ + try{ - /* - * Create the case metadata (.aut) file. - */ - CaseMetadata metadata; - try { - metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName); - } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_creationException(), ex); - } + // The shared lock uses case directory + // The shared lock needs to be created on a special thread so it can be released + // from the same thread. + Future future = getCurrentCaseExecutor().submit(() -> { + currentCaseLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir); + if (null == currentCaseLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + return null; + }); + future.get(); - /* - * Create the case database. - */ - SleuthkitCase db = null; - try { - if (caseType == CaseType.SINGLE_USER_CASE) { - db = SleuthkitCase.newCase(dbName); - } else if (caseType == CaseType.MULTI_USER_CASE) { - db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); + // The exclusive lock uses the unique case name. + // This lock does not need to be on a special thread since it will be released before + // leaving this method + exclusiveResourceLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE, + dbName, 12, TimeUnit.HOURS); + if (null == exclusiveResourceLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + } catch (Exception ex){ + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); + /* - * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly - * messages, so propagate the exception message. + * Create the case metadata (.aut) file. */ - throw new CaseActionException(ex.getMessage(), ex); //NON-NLS - } catch (UserPreferencesException ex) { - logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + CaseMetadata metadata; + try { + metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_creationException(), ex); + } + + /* + * Create the case database. + */ + SleuthkitCase db = null; + try { + if (caseType == CaseType.SINGLE_USER_CASE) { + db = SleuthkitCase.newCase(dbName); + } else if (caseType == CaseType.MULTI_USER_CASE) { + db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + /* + * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly + * messages, so propagate the exception message. + */ + throw new CaseActionException(ex.getMessage(), ex); //NON-NLS + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + } + + Case newCase = new Case(metadata, db); + changeCurrentCase(newCase); + caseCreatedSuccessfully = true; + + logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + } finally { + // Release the exclusive resource lock + try { + if (exclusiveResourceLock != null) { + exclusiveResourceLock.release(); + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseName), exx); + } + + // If an error occurred while opening the case, release the shared case lock as well + if(! caseCreatedSuccessfully){ + Future future = getCurrentCaseExecutor().submit(() -> { + try { + if (currentCaseLock != null) { + currentCaseLock.release(); + currentCaseLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseName), exx); + } + return null; + }); + try{ + future.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); + } + } } - - Case newCase = new Case(metadata, db); - changeCurrentCase(newCase); - - logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS } /** @@ -1143,6 +1240,8 @@ public class Case implements SleuthkitCase.ErrorObserver { */ public static void open(String caseMetadataFilePath) throws CaseActionException { logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + CoordinationService.Lock exclusiveResourceLock = null; + boolean caseOpenedSuccessfully = false; /* * Verify the extension of the case metadata file. @@ -1158,6 +1257,39 @@ public class Case implements SleuthkitCase.ErrorObserver { CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); CaseType caseType = metadata.getCaseType(); + /* + * If this is a multi-user case, acquire two locks: + * - a shared lock on the case to prevent it from being deleted while open + * - an exclusive lock to prevent multiple clients from executing openCase simultaneously + */ + if(caseType == CaseType.MULTI_USER_CASE){ + try{ + + // The shared lock uses the case directory + // The shared lock needs to be created on a special thread so it can be released + // from the same thread. + Future future = getCurrentCaseExecutor().submit(() -> { + currentCaseLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); + if (null == currentCaseLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + return null; + }); + future.get(); + + // The exclusive lock uses the unique case name + // This lock does not need to be on a special thread since it will be released before + // leaving this method + exclusiveResourceLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE, + metadata.getCaseDatabaseName(), 12, TimeUnit.HOURS); + if (null == exclusiveResourceLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + } catch (Exception ex){ + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + } + /* * Open the case database. */ @@ -1221,6 +1353,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } Case openedCase = new Case(metadata, db); changeCurrentCase(openedCase); + caseOpenedSuccessfully = true; } catch (CaseMetadataException ex) { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS @@ -1233,6 +1366,36 @@ public class Case implements SleuthkitCase.ErrorObserver { * user-friendly messages, so propagate the exception message. */ throw new CaseActionException(ex.getMessage(), ex); + } finally { + // Release the exclusive resource lock + try { + if (exclusiveResourceLock != null) { + exclusiveResourceLock.release(); + exclusiveResourceLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseMetadataFilePath), exx); + } + + // If an error occurred while opening the case, release the shared case lock as well + if(! caseOpenedSuccessfully){ + Future future = getCurrentCaseExecutor().submit(() -> { + try { + if (currentCaseLock != null) { + currentCaseLock.release(); + currentCaseLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseMetadataFilePath), exx); + } + return null; + }); + try{ + future.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); + } + } } } @@ -1433,12 +1596,23 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @param casePath A case directory path. * - * @return True if the deleteion succeeded, false otherwise. + * @return True if the deletion succeeded, false otherwise. */ static boolean deleteCaseDirectory(File casePath) { logger.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS return FileUtil.deleteDir(casePath); } + + /** + * Get the single thread executor for the current case, creating it if necessary. + * @return The executor + */ + private static ExecutorService getCurrentCaseExecutor(){ + if(currentCaseExecutor == null){ + currentCaseExecutor = Executors.newSingleThreadExecutor(); + } + return currentCaseExecutor; + } /** * Gets the time zone(s) of the image data source(s) in this case. diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index a433d17cfd..39c5c25ba6 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -50,7 +50,8 @@ public final class CoordinationService { CASES("cases"), MANIFESTS("manifests"), - CONFIG("config"); + CONFIG("config"), + RESOURCE("resource"); private final String displayName; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java similarity index 86% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java rename to Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java index 1b1c4a1227..e1f2a3df42 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java @@ -16,15 +16,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.experimental.autoingest; +package org.sleuthkit.autopsy.coordinationservice; /** * Namespace elements for auto ingest coordination service nodes. */ -final class CoordinationServiceNamespace { +public final class CoordinationServiceNamespace { private static final String ROOT = "autopsy"; - static String getRoot() { + public static String getRoot() { return ROOT; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index c56bec23b5..3165ad23c9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 5c121d55c3..867ae631e8 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java index 400f297476..2cae50ef1a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java @@ -18,10 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -38,7 +35,6 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseNewAction; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; /** * Handles opening, locking, and unlocking cases in review mode. Instances of @@ -47,7 +43,7 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Coordinatio * dispatch thread (EDT). Because of the tight coupling to the UI, exception * messages are deliberately user-friendly. */ -final class ReviewModeCaseManager implements PropertyChangeListener { +final class ReviewModeCaseManager { /* * Provides uniform exceptions with user-friendly error messages. @@ -77,14 +73,7 @@ final class ReviewModeCaseManager implements PropertyChangeListener { */ synchronized static ReviewModeCaseManager getInstance() { if (instance == null) { - /* - * Two stage construction is used here to avoid allowing "this" - * reference to escape from the constructor via registering as an - * PropertyChangeListener. This is to ensure that a partially - * constructed manager is not published to other threads. - */ instance = new ReviewModeCaseManager(); - Case.addPropertyChangeListener(instance); } return instance; } @@ -149,17 +138,7 @@ final class ReviewModeCaseManager implements PropertyChangeListener { * requirement */ synchronized void openCaseInEDT(Path caseMetadataFilePath) throws ReviewModeCaseManagerException { - Path caseFolderPath = caseMetadataFilePath.getParent(); try { - /* - * Acquire a lock on the case folder. If the lock cannot be - * acquired, the case cannot be opened. - */ - currentCaseLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseFolderPath.toString()); - if (null == currentCaseLock) { - throw new ReviewModeCaseManagerException("Could not get shared access to multi-user case folder"); - } - /* * Open the case. */ @@ -177,64 +156,8 @@ final class ReviewModeCaseManager implements PropertyChangeListener { CallableSystemAction.get(AddImageAction.class).setEnabled(false); }); - } catch (CoordinationServiceException | ReviewModeCaseManagerException | CaseActionException ex) { - /* - * Release the coordination service lock on the case folder. - */ - try { - if (currentCaseLock != null) { - currentCaseLock.release(); - currentCaseLock = null; - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error deleting legacy LOCKED state file for case at %s", caseFolderPath), exx); - } - - if (ex instanceof CoordinationServiceException) { - throw new ReviewModeCaseManagerException("Could not get access to the case folder from the coordination service, contact administrator", ex); - } else if (ex instanceof IOException) { - throw new ReviewModeCaseManagerException("Could not write to the case folder, contact adminstrator", ex); - } else if (ex instanceof CaseActionException) { - /* - * CaseActionExceptions have user friendly error messages. - */ - throw new ReviewModeCaseManagerException(String.format("Could not open the case (%s), contract administrator", ex.getMessage()), ex); - } else if (ex instanceof ReviewModeCaseManagerException) { - throw (ReviewModeCaseManagerException) ex; - } + } catch (CaseActionException ex) { + throw new ReviewModeCaseManagerException(String.format("Could not open the case (%s), contract administrator", ex.getMessage()), ex); } } - - /** - * @inheritDoc - */ - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString()) - && null != evt.getOldValue() - && null == evt.getNewValue()) { - /* - * When a case is closed, release the coordination service lock on - * the case folder. This must be done in the EDT because it was - * acquired in the EDT via openCase(). - */ - if (null != currentCaseLock) { - try { - SwingUtilities.invokeAndWait(() -> { - try { - currentCaseLock.release(); - currentCaseLock = null; - } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to release the coordination service lock with path %s", currentCaseLock.getNodePath()), ex); - currentCaseLock = null; - } - }); - } catch (InterruptedException | InvocationTargetException ex) { - logger.log(Level.SEVERE, String.format("Failed to release the coordination service lock with path %s", currentCaseLock.getNodePath()), ex); - currentCaseLock = null; - } - } - } - } - } From 5b52aa2ac82a8120dcfdb4a6341605f440564c31 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 11 Jan 2017 17:34:26 -0500 Subject: [PATCH 3/3] Add comments to ImageNode and VolumeNode PCLs --- .../sleuthkit/autopsy/datamodel/ImageNode.java | 16 +++++++++------- .../sleuthkit/autopsy/datamodel/VolumeNode.java | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index aee8368831..8b2f3b9ae7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -77,9 +77,9 @@ public class ImageNode extends AbstractContentNode { String imgName = nameForImage(img); this.setDisplayName(imgName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hard-drive-icon.jpg"); //NON-NLS - + // Listen for ingest events so that we can detect new added files (e.g. carved) - IngestManager.getInstance().addIngestModuleEventListener(pcl); + IngestManager.getInstance().addIngestModuleEventListener(pcl); // Listen for case events so that we can detect when case is closed Case.addPropertyChangeListener(pcl); } @@ -101,8 +101,6 @@ public class ImageNode extends AbstractContentNode { "ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",}) public Action[] getActions(boolean context) { - - List actionsList = new ArrayList(); for (Action a : super.getActions(true)) { actionsList.add(a); @@ -215,7 +213,12 @@ public class ImageNode extends AbstractContentNode { public String getItemType() { return getClass().getName(); } - + + /* + * This property change listener refreshes the tree when a new file is + * carved out of this image (i.e, the image is being treated as raw bytes + * and was ingested by the RawDSProcessor). + */ private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); @@ -255,6 +258,5 @@ public class ImageNode extends AbstractContentNode { } } }; - - + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index 161a972743..ba37cd4f09 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -80,6 +80,10 @@ public class VolumeNode extends AbstractContentNode { Case.removePropertyChangeListener(pcl); } + /* + * This property change listener refreshes the tree when a new file is + * carved out of the unallocated space of this volume. + */ private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName();