From bed99ecbf61a47a1a4b1bdbba751b0d6ecdbeb60 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 22 Apr 2024 13:48:51 -0400 Subject: [PATCH] fixes --- .../casemodule/Bundle.properties-MERGED | 9 +- .../sleuthkit/autopsy/casemodule/Case.java | 95 +++++----- .../autopsy/casemodule/TskLockResources.java | 162 ------------------ 3 files changed, 45 insertions(+), 221 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/TskLockResources.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 5dfaba3f43..14465bba03 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -111,13 +111,8 @@ Case_checkImagePaths_exceptionOccurred=An exception occurred while checking if i # {0} - paths Case_checkImagePaths_noPaths=The following images had no associated paths: {0} # {0} - appplicationName -Case_createCaseDatabase_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy. -Case_createCaseDatabase_fileLock_concurrentAccessException_defaultApp=another application -Case_createCaseDatabase_fileLock_ioException=An error occurred while trying to get an exclusive lock on the case. -# {0} - appplicationName -Case_openCaseDataBase_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy. -Case_openCaseDataBase_fileLock_concurrentAccessException_defaultApp=another application -Case_openCaseDataBase_fileLock_ioException=An error occurred while trying to get an exclusive lock on the case. +Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy. +Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException_defaultApp=another application CaseDetailsPanel.casePanel.border.title=Case CaseDetailsPanel.examinerLabel.text=Name: CaseDetailsPanel.examinerPanel.border.title=Examiner diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 3e0a84e93f..50dcca3f6f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -31,9 +31,7 @@ import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; -import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.nio.channels.OverlappingFileLockException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; @@ -78,7 +76,6 @@ import org.sleuthkit.autopsy.actions.OpenOutputFolderAction; import org.sleuthkit.autopsy.appservices.AutopsyService; import org.sleuthkit.autopsy.appservices.AutopsyService.CaseContext; import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException; -import org.sleuthkit.autopsy.casemodule.TskLockResources.ConcurrentDbAccessException; import org.sleuthkit.autopsy.datasourcesummary.ui.DataSourceSummaryAction; import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent; import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent; @@ -148,6 +145,7 @@ import org.sleuthkit.autopsy.timeline.OpenTimelineAction; import org.sleuthkit.autopsy.timeline.events.TimelineEventAddedEvent; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.ConcurrentDbAccessException; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentStreamProvider; import org.sleuthkit.datamodel.ContentTag; @@ -199,8 +197,7 @@ public class Case { private final SleuthkitEventListener sleuthkitEventListener; private CollaborationMonitor collaborationMonitor; private Services caseServices; - - private TskLockResources tskLockResources = null; + private volatile boolean hasDataSource = false; private volatile boolean hasData = false; @@ -214,7 +211,6 @@ public class Case { mainFrame = WindowManager.getDefault().getMainWindow(); }); } - /** * An enumeration of case types. @@ -774,7 +770,6 @@ public class Case { || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"") || caseName.contains("<") || caseName.contains(">") || caseName.contains("|")); } - /** * Creates a new case and makes it the current case. @@ -2731,11 +2726,7 @@ public class Case { "Case.progressMessage.creatingCaseDatabase=Creating case database...", "# {0} - exception message", "Case.exceptionMessage.couldNotGetDbServerConnectionInfo=Failed to get case database server conneciton info:\n{0}.", "# {0} - exception message", "Case.exceptionMessage.couldNotCreateCaseDatabase=Failed to create case database:\n{0}.", - "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}.", - "Case_createCaseDatabase_fileLock_ioException=An error occurred while trying to get an exclusive lock on the case.", - "# {0} - appplicationName", - "Case_createCaseDatabase_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy.", - "Case_createCaseDatabase_fileLock_concurrentAccessException_defaultApp=another application" + "# {0} - exception message", "Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}." }) private void createCaseDatabase(ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDatabase()); @@ -2746,17 +2737,7 @@ public class Case { * with a standard name, physically located in the case * directory. */ - try { - this.tskLockResources = TskLockResources.tryAcquireFileLock(metadata.getCaseDirectory(), UserPreferences.getAppName()); - } catch (IOException | OverlappingFileLockException ex) { - throw new CaseActionException(Bundle.Case_openCaseDataBase_fileLock_ioException(), ex); - } catch (ConcurrentDbAccessException ex) { - throw new CaseActionException(Bundle.Case_openCaseDataBase_fileLock_concurrentAccessException( - StringUtils.defaultIfBlank(ex.getConflictingApplicationName(), - Bundle.Case_openCaseDataBase_fileLock_concurrentAccessException_defaultApp()) - ), ex); - } - caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME).toString()); + caseDb = SleuthkitCase.newCase(Paths.get(metadata.getCaseDirectory(), SINGLE_USER_CASE_DB_NAME, APP_NAME).toString()); metadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME); } else { /* @@ -2768,6 +2749,7 @@ public class Case { metadata.setCaseDatabaseName(caseDb.getDatabaseName()); } } catch (TskCoreException ex) { + throwIfConcurrentDbAccessException(ex); throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseDatabase(ex.getLocalizedMessage()), ex); } catch (UserPreferencesException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex); @@ -2792,17 +2774,13 @@ public class Case { "# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.", "# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.", "Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.", - "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User.", - "Case_openCaseDataBase_fileLock_ioException=An error occurred while trying to get an exclusive lock on the case.", - "# {0} - appplicationName", - "Case_openCaseDataBase_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy.", - "Case_openCaseDataBase_fileLock_concurrentAccessException_defaultApp=another application" + "Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User." }) private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException { progressIndicator.progress(Bundle.Case_progressMessage_openingCaseDatabase()); try { String databaseName = metadata.getCaseDatabaseName(); - + ContentStreamProvider contentProvider = loadContentProvider(metadata.getContentProviderName()); if (StringUtils.isNotBlank(metadata.getContentProviderName()) && contentProvider == null) { if (metadata.getContentProviderName().trim().toUpperCase().startsWith(CT_PROVIDER_PREFIX.toUpperCase())) { @@ -2810,19 +2788,9 @@ public class Case { } throw new CaseActionException(Bundle.Case_exceptionMessage_contentProviderCouldNotBeFound()); } - + if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - try { - this.tskLockResources = TskLockResources.tryAcquireFileLock(metadata.getCaseDirectory(), UserPreferences.getAppName()); - } catch (IOException | OverlappingFileLockException ex) { - throw new CaseActionException(Bundle.Case_openCaseDataBase_fileLock_ioException(), ex); - } catch (ConcurrentDbAccessException ex) { - throw new CaseActionException(Bundle.Case_openCaseDataBase_fileLock_concurrentAccessException( - StringUtils.defaultIfBlank(ex.getConflictingApplicationName(), - Bundle.Case_openCaseDataBase_fileLock_concurrentAccessException_defaultApp()) - ), ex); - } - caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath(), contentProvider); + caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath(), contentProvider, APP_NAME); } else if (UserPreferences.getIsMultiUserModeEnabled()) { caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory(), contentProvider); } else { @@ -2834,7 +2802,39 @@ public class Case { } catch (UserPreferencesException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotGetDbServerConnectionInfo(ex.getLocalizedMessage()), ex); } catch (TskCoreException ex) { - throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex); + throwIfConcurrentDbAccessException(ex); + throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenCaseDatabase(ex.getLocalizedMessage()), ex); + } + } + + + /** + * Throws a CaseActionException if the exception or any nested exception is a ConcurrentDbAccessException (max depth of 10) + * @param ex The exception. + * @throws CaseActionException Thrown if there is a concurrent db access exception. + */ + @Messages({ + "# {0} - appplicationName", + "Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException=The case is open in {0}. Please close it before attempting to open it in Autopsy.", + "Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException_defaultApp=another application" + }) + private void throwIfConcurrentDbAccessException(Exception ex) throws CaseActionException { + ConcurrentDbAccessException concurrentEx = null; + Throwable curEx = ex; + // max depth search for a concurrent db access exception will be 10 + for (int i = 0; i < 10; i++) { + if (curEx instanceof ConcurrentDbAccessException foundEx) { + concurrentEx = foundEx; + break; + } + curEx = curEx.getCause(); + } + + if (concurrentEx != null) { + throw new CaseActionException(Bundle.Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException( + StringUtils.defaultIfBlank(concurrentEx.getConflictingApplicationName(), + Bundle.Case_throwIfConcurrentDbAccessException_fileLock_concurrentAccessException_defaultApp()) + ), concurrentEx); } } @@ -3132,17 +3132,8 @@ public class Case { collaborationMonitor.shutdown(); } eventPublisher.closeRemoteEventChannel(); - } - - if (this.tskLockResources != null) { - try { - this.tskLockResources.close(); - this.tskLockResources = null; - } catch (Exception ex) { - logger.log(Level.WARNING, "There was an error closing the TSK case lock resources", ex); - } } - + /* * Allow all registered application services providers to close * resources related to the case. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/TskLockResources.java b/Core/src/org/sleuthkit/autopsy/casemodule/TskLockResources.java deleted file mode 100644 index 78c36cd984..0000000000 --- a/Core/src/org/sleuthkit/autopsy/casemodule/TskLockResources.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2024 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.casemodule; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import org.apache.commons.lang3.StringUtils; - -/** - * The resources associated with the file lock for the TSK database. - */ -class TskLockResources implements AutoCloseable { - - private static final String LOCK_FILE_NAME = "lock"; - - private File lockFile = null; - private RandomAccessFile lockFileRaf = null; - private FileChannel lockFileChannel = null; - private FileLock lockFileLock = null; - - /** - * Constructor. - * - * @param lockFile The lock file File reference. - * @param lockFileRef The lock file random access file reference. - * @param lockFileChannel The lock file file channel. - * @param lockFileLock The lock file file lock. - */ - TskLockResources(File lockFile, RandomAccessFile lockFileRaf, FileChannel lockFileChannel, FileLock lockFileLock) { - this.lockFile = lockFile; - this.lockFileRaf = lockFileRaf; - this.lockFileChannel = lockFileChannel; - this.lockFileLock = lockFileLock; - } - - /** - * Try to acquire a lock to the lock file in the case directory. - * - * @param caseDir The case directory that the autopsy.db is in. - * @return The lock file resources to be closed. - * @throws IllegalAccessException - * @throws IOException - */ - static TskLockResources tryAcquireFileLock(String caseDir, String applicationName) throws ConcurrentDbAccessException, IOException, OverlappingFileLockException { - // get the lock file path - File lockFile = new File(caseDir, LOCK_FILE_NAME); - // make directories leading up to that - lockFile.getParentFile().mkdirs(); - - // if the lock file exists - if (lockFile.isFile() && !lockFile.canWrite()) { - // get the random access file as read only - RandomAccessFile lockFileRaf = new RandomAccessFile(lockFile, "r"); - throw ConcurrentDbAccessException.createForFile(lockFile.getAbsolutePath(), lockFileRaf); - } else { - RandomAccessFile lockFileRaf = new RandomAccessFile(lockFile, "rw"); - FileChannel lockFileChannel = lockFileRaf.getChannel(); - FileLock lockFileLock = lockFileChannel == null - ? null - : lockFileChannel.tryLock(1024L, 1L, false); - - if (lockFileLock != null) { - lockFileRaf.setLength(0); - lockFileRaf.writeChars(applicationName); - return new TskLockResources(lockFile, lockFileRaf, lockFileChannel, lockFileLock); - } else { - throw ConcurrentDbAccessException.createForFile(lockFile.getAbsolutePath(), lockFileRaf); - } - } - } - - @Override - public void close() throws Exception { - // close lock file resources in reverse acquisition order - if (this.lockFileLock != null) { - this.lockFileLock.close(); - this.lockFileLock = null; - } - - if (this.lockFileChannel != null) { - this.lockFileChannel.close(); - this.lockFileChannel = null; - } - - if (this.lockFileRaf != null) { - this.lockFileRaf.close(); - this.lockFileRaf = null; - } - - if (this.lockFile != null) { - this.lockFile.delete(); - this.lockFile = null; - } - } - - /** - * An exception thrown if the database is currently in use. - */ - static class ConcurrentDbAccessException extends Exception { - - private final String conflictingApplicationName; - - /** - * Creates a ConcurrentDbAccessException from the lock file path and the - * random access file of that path whose contents are the application - * name. - * - * @param lockFilePath The lock file path. - * @param lockFileRaf The lock file random access file. - * @return The exception - * @throws IOException - */ - static ConcurrentDbAccessException createForFile(String lockFilePath, RandomAccessFile lockFileRaf) throws IOException { - StringBuffer buffer = new StringBuffer(); - while (lockFileRaf.getFilePointer() < lockFileRaf.length()) { - buffer.append(lockFileRaf.readLine() + System.lineSeparator()); - } - String conflictingApplication = buffer.toString().trim(); - String message = "Unable to acquire lock on " + lockFilePath + "." + (StringUtils.isNotBlank(conflictingApplication) ? (" Database is already open in " + conflictingApplication + ".") : ""); - return new ConcurrentDbAccessException(message, conflictingApplication); - } - - /** - * Constructor. - * - * @param message The exception message. - * @param conflictingApplicationName The conflicting application name - * (or null if unknown). - */ - ConcurrentDbAccessException(String message, String conflictingApplicationName) { - super(message); - this.conflictingApplicationName = conflictingApplicationName; - } - - /** - * @return The conflicting application name (or null if unknown). - */ - public String getConflictingApplicationName() { - return conflictingApplicationName; - } - } -}