From 249d91f0a1545114db20722bdc35b1ae8f7af7a7 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Wed, 28 Aug 2024 15:46:47 -0400 Subject: [PATCH] GP-4867 Added BSim Server connection toggle for H2 and Postgres. Fixed various related bugs. --- Ghidra/Features/BSim/certification.manifest | 2 + .../Features/BSim/data/bsim.theme.properties | 3 + .../AddProgramToH2BSimDatabaseScript.java | 39 +++-- .../CreateH2BSimDatabaseScript.java | 55 ++++--- .../topics/BSimSearchPlugin/BSimSearch.html | 16 +- .../images/ManageServersDialog.png | Bin 16072 -> 20021 bytes .../features/bsim/gui/BSimSearchPlugin.java | 6 +- .../dialog => }/BSimServerManager.java | 108 +++++++++---- .../dialog/AbstractBSimSearchDialog.java | 5 +- .../gui/search/dialog/BSimOverviewDialog.java | 5 +- .../gui/search/dialog/BSimSearchDialog.java | 5 +- .../gui/search/dialog/BSimServerDialog.java | 145 +++++++++++++----- .../search/dialog/BSimServerTableModel.java | 144 +++++++++++------ .../search/dialog/ConnectionPoolStatus.java | 44 ++++++ .../dialog/CreateBsimServerInfoDialog.java | 7 +- .../bsim/query/BSimClientFactory.java | 9 +- .../bsim/query/BSimJDBCDataSource.java | 15 +- .../BSimPostgresDBConnectionManager.java | 26 ++-- .../features/bsim/query/FunctionDatabase.java | 23 ++- .../file/BSimH2FileDBConnectionManager.java | 60 +++++--- .../src/main/resources/images/connect.png | Bin 0 -> 748 bytes .../src/main/resources/images/disconnect.png | Bin 0 -> 796 bytes .../BSimSearchPluginScreenShots.java | 7 +- .../bsim/gui/BSimSearchPluginTestHelper.java | 5 +- .../features/bsim/gui/QueryFilterTest.java | 9 +- .../gui/overview/BSimOverviewTestHelper.java | 7 +- .../search/dialog/BSimFilterPanelTest.java | 10 +- .../dialog/BSimSearchDialogTestHelper.java | 7 +- .../file}/BSimH2DatabaseManagerTest.java | 11 +- .../bsim}/query/test/BSimServerTest.java | 6 +- .../bsim}/query/test/BSimServerTestUtil.java | 6 +- 31 files changed, 530 insertions(+), 255 deletions(-) rename Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/{search/dialog => }/BSimServerManager.java (65%) create mode 100644 Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java create mode 100644 Ghidra/Features/BSim/src/main/resources/images/connect.png create mode 100644 Ghidra/Features/BSim/src/main/resources/images/disconnect.png rename Ghidra/Features/BSim/src/test.slow/java/ghidra/{query/inmemory => features/bsim/query/file}/BSimH2DatabaseManagerTest.java (98%) rename Ghidra/Features/BSim/src/test.slow/java/ghidra/{ => features/bsim}/query/test/BSimServerTest.java (99%) rename Ghidra/Features/BSim/src/test.slow/java/ghidra/{ => features/bsim}/query/test/BSimServerTestUtil.java (99%) diff --git a/Ghidra/Features/BSim/certification.manifest b/Ghidra/Features/BSim/certification.manifest index 12c5480cf9..137ac8dcbf 100755 --- a/Ghidra/Features/BSim/certification.manifest +++ b/Ghidra/Features/BSim/certification.manifest @@ -42,6 +42,8 @@ src/main/help/help/topics/BSimSearchPlugin/images/BSimSearchDialog.png||GHIDRA|| src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png||GHIDRA||||END| src/main/resources/bsim.log4j.xml||GHIDRA||||END| src/main/resources/images/checkmark_yellow.gif||GHIDRA||||END| +src/main/resources/images/connect.png||FAMFAMFAM Icons - CC 2.5||||END| +src/main/resources/images/disconnect.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/flag_green.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/preferences-desktop-user-password.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/preferences-web-browser-shortcuts-32.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| diff --git a/Ghidra/Features/BSim/data/bsim.theme.properties b/Ghidra/Features/BSim/data/bsim.theme.properties index c0efedbea0..497af941d8 100644 --- a/Ghidra/Features/BSim/data/bsim.theme.properties +++ b/Ghidra/Features/BSim/data/bsim.theme.properties @@ -14,4 +14,7 @@ icon.bsim.results.status.ignored = checkmark_yellow.gif icon.bsim.functions.table = FunctionScope.gif +icon.bsim.connected = connect.png +icon.bsim.disconnected = disconnect.png + [Dark Defaults] diff --git a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java index 648768ce7c..adbe50e621 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java @@ -4,9 +4,9 @@ * 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. @@ -71,22 +71,17 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript { askValues("Select Database File", null, values); File h2DbFile = values.getFile(DATABASE); + BSimServerInfo serverInfo = + new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath()); - FunctionDatabase h2Database = null; - try { - BSimServerInfo serverInfo = - new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath()); - h2Database = BSimClientFactory.buildClient(serverInfo, false); - BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - if (bds == null) { - popup(h2DbFile.getAbsolutePath() + " is not an H2 database file"); - return; - } - if (bds.getActiveConnections() > 0) { - popup("There is an existing connection to the database."); - return; - } + BSimH2FileDataSource existingBDS = + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (existingBDS != null && existingBDS.getActiveConnections() > 0) { + popup("There is an existing connection to the database."); + return; + } + + try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) { h2Database.initialize(); DatabaseInformation dbInfo = h2Database.getInfo(); @@ -169,11 +164,13 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript { } finally { - if (h2Database != null) { - h2Database.close(); + if (existingBDS == null) { + // Dispose database source if it did not previously exist BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - bds.dispose(); + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (bds != null) { + bds.dispose(); + } } } } diff --git a/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java index ef332f6d64..f30b1c65d5 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java @@ -4,9 +4,9 @@ * 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. @@ -31,7 +31,6 @@ import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.protocol.*; import ghidra.util.MessageType; -import ghidra.util.Msg; public class CreateH2BSimDatabaseScript extends GhidraScript { private static final String NAME = "Database Name"; @@ -80,31 +79,27 @@ public class CreateH2BSimDatabaseScript extends GhidraScript { askValues("Enter Database Parameters", "Enter values required to create a new BSim H2 database.", values); - FunctionDatabase h2Database = null; - try { - String databaseName = values.getString(NAME); - File dbDir = values.getFile(DIRECTORY); - String template = values.getChoice(DATABASE_TEMPLATE); - String functionTagsCSV = values.getString(FUNCTION_TAGS); - List tags = parseCSV(functionTagsCSV); + String databaseName = values.getString(NAME); + File dbDir = values.getFile(DIRECTORY); + String template = values.getChoice(DATABASE_TEMPLATE); + String functionTagsCSV = values.getString(FUNCTION_TAGS); + List tags = parseCSV(functionTagsCSV); - String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES); - List cats = parseCSV(exeCatCSV); + String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES); + List cats = parseCSV(exeCatCSV); - File dbFile = new File(dbDir, databaseName); + File dbFile = new File(dbDir, databaseName); + BSimServerInfo serverInfo = + new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath()); - BSimServerInfo serverInfo = - new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath()); - h2Database = BSimClientFactory.buildClient(serverInfo, false); - BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - if (bds.getActiveConnections() > 0) { - //if this happens, there is a connection to the database but the - //database file was deleted - Msg.showError(this, null, "Connection Error", - "There is an existing connection to the database!"); - return; - } + BSimH2FileDataSource existingBDS = + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (existingBDS != null && existingBDS.getActiveConnections() > 0) { + popup("There is an existing connection to the database."); + return; + } + + try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) { CreateDatabase command = new CreateDatabase(); command.info = new DatabaseInformation(); @@ -140,11 +135,13 @@ public class CreateH2BSimDatabaseScript extends GhidraScript { popup("Database " + values.getString(NAME) + " created successfully!"); } finally { - if (h2Database != null) { - h2Database.close(); + if (existingBDS == null) { + // Dispose database source if it did not previously exist BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - bds.dispose(); + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (bds != null) { + bds.dispose(); + } } } diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html index bdbfd102af..ffecfeb75d 100644 --- a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html +++ b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html @@ -57,18 +57,24 @@ entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip and port (if applicable), and finally the number of active connections.

-

There are three primary actions for this dialog:

+

There are four primary actions for this dialog:

    -
  •  Add a new database/server definition - a - Define Server Dialog will be shown.
  • +
  •  Add a new BSim database/server definition - an + Add BSim Server Dialog will be shown.
  •  Delete a database/server definition - The - selected entry will be deleted.
  • + selected entry will be deleted. This action will force an immediate disconnect for an + active/idle connection and should be used with care. + +
  •   + Connect or disconnect an inactive database/server connection. This action is not supported + by Elastic database servers.
  •  Change password - A change password - dialog will appear for the selected entry
  • + dialog will appear for the selected database server. Action is disabled for databases + which do not use a password (e.g., Local H2 File database).

Defining a new BSim server/database

diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png index abe6af84947c4d99306483fc57bb2542981e0b53..59f713dc8cd3d62ac6a1f261f92a67b83a88668e 100644 GIT binary patch literal 20021 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 searchResultsProviders = new HashSet<>(); private Set overviewProviders = new HashSet<>(); - private BSimServerManager serverManager = new BSimServerManager(); + private BSimServerManager serverManager = BSimServerManager.getBSimServerManager(); private BSimSearchService searchService; private BSimServerCache lastUsedServerCache = null; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java similarity index 65% rename from Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerManager.java rename to Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java index 9e1559af07..df21cbbbfb 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java @@ -4,25 +4,25 @@ * 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 ghidra.features.bsim.gui.search.dialog; +package ghidra.features.bsim.gui; import java.io.File; import java.io.IOException; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; -import ghidra.features.bsim.query.BSimPostgresDBConnectionManager; +import ghidra.features.bsim.gui.search.dialog.BSimServerManagerListener; +import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.BSimPostgresDBConnectionManager.BSimPostgresDataSource; -import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; @@ -36,12 +36,24 @@ import ghidra.util.Swing; * Managers BSim database server definitions and connections */ public class BSimServerManager { - // TODO: Do not allow removal of active server. Dispose data source when removed. + + private static BSimServerManager instance; + + /** + * Get static singleton instance for BSimServerManager + * @return BSimServerManager instance + */ + static synchronized BSimServerManager getBSimServerManager() { + if (instance == null) { + instance = new BSimServerManager(); + } + return instance; + } private Set serverInfos = new HashSet<>(); private List listeners = new CopyOnWriteArrayList<>(); - public BSimServerManager() { + private BSimServerManager() { List files = Application.getUserSettingsFiles("bsim", ".server.properties"); for (File file : files) { BSimServerInfo info = readBsimServerInfoFile(file); @@ -51,6 +63,10 @@ public class BSimServerManager { } } + /** + * Get list of defined servers. Method must be invoked from swing thread only. + * @return list of defined servers + */ public Set getServerInfos() { return new HashSet<>(serverInfos); } @@ -108,6 +124,10 @@ public class BSimServerManager { return serverFile.delete(); } + /** + * Add server to list. Method must be invoked from swing thread only. + * @param newServerInfo new BSim DB server + */ public void addServer(BSimServerInfo newServerInfo) { if (saveBSimServerInfo(newServerInfo)) { serverInfos.add(newServerInfo); @@ -115,28 +135,42 @@ public class BSimServerManager { } } - public boolean removeServer(BSimServerInfo info, boolean force) { + private static boolean disposeServer(BSimServerInfo info, boolean force) { DBType dbType = info.getDBType(); if (dbType == DBType.file) { - BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSource(info); - int active = ds.getActiveConnections(); - if (active != 0) { - if (!force) { + BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSourceIfExists(info); + if (ds != null) { + int active = ds.getActiveConnections(); + if (active != 0 && !force) { return false; } ds.dispose(); } } else if (dbType == DBType.postgres) { - BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSource(info); - int active = ds.getActiveConnections(); - if (active != 0) { - if (!force) { + BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSourceIfExists(info); + if (ds != null) { + int active = ds.getActiveConnections(); + if (active != 0 && !force) { return false; } ds.dispose(); } } + return true; + } + + /** + * Remove BSim DB server from list. Method must be invoked from swing thread only. + * Specified server datasource will be dispose unless it is active or force is true. + * @param info BSim DB server to be removed + * @param force true if server datasource should be disposed even when active. + * @return true if server disposed and removed from list + */ + public boolean removeServer(BSimServerInfo info, boolean force) { + if (!disposeServer(info, force)) { + return false; + } if (serverInfos.remove(info)) { removeServerFileFromSettings(info); notifyServerListChanged(); @@ -160,26 +194,38 @@ public class BSimServerManager { }); } - public static int getActiveConnections(BSimServerInfo serverInfo) { + /** + * Convenience method to get existing BSim JDBC datasource + * @param serverInfo BSim DB server info + * @return BSim DB datasource or null if not instantiated or server does not support a + * {@link BSimJDBCDataSource}. + */ + public static BSimJDBCDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { switch (serverInfo.getDBType()) { case postgres: - BSimPostgresDataSource postgresDs = - BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo); - if (postgresDs != null) { - return postgresDs.getActiveConnections(); - } - break; + return BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo); case file: - BSimH2FileDataSource h2FileDs = - BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); - if (h2FileDs != null) { - return h2FileDs.getActiveConnections(); - } - break; + return BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); default: - break; + return null; + } + } + + /** + * Convenience method to get a new or existing BSim JDBC datasource + * @param serverInfo BSim DB server info + * @return BSim DB datasource or null if server does not support a + * {@link BSimJDBCDataSource}. + */ + public static BSimJDBCDataSource getDataSource(BSimServerInfo serverInfo) { + switch (serverInfo.getDBType()) { + case postgres: + return BSimPostgresDBConnectionManager.getDataSource(serverInfo); + case file: + return BSimH2FileDBConnectionManager.getDataSource(serverInfo); + default: + return null; } - return -1; } } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java index ff999ee822..9ed6829018 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java @@ -4,9 +4,9 @@ * 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. @@ -30,6 +30,7 @@ import docking.widgets.EmptyBorderButton; import docking.widgets.combobox.GComboBox; import docking.widgets.textfield.FloatingPointTextField; import generic.theme.Gui; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.features.bsim.query.facade.QueryDatabaseException; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java index 2b44b19bbb..a4afa85649 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java @@ -4,9 +4,9 @@ * 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. @@ -16,6 +16,7 @@ package ghidra.features.bsim.gui.search.dialog; import ghidra.features.bsim.gui.BSimSearchPlugin; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java index d0df4f8bba..9b9318638e 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java @@ -4,9 +4,9 @@ * 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. @@ -30,6 +30,7 @@ import docking.widgets.textfield.IntegerTextField; import generic.theme.GIcon; import ghidra.app.services.GoToService; import ghidra.features.bsim.gui.BSimSearchPlugin; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.gui.filters.BSimFilterType; import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.framework.plugintool.PluginTool; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java index a2c90f1c67..bec9aeee2e 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java @@ -4,9 +4,9 @@ * 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. @@ -16,21 +16,25 @@ package ghidra.features.bsim.gui.search.dialog; import java.awt.BorderLayout; +import java.sql.Connection; +import java.sql.SQLException; import javax.swing.*; import org.bouncycastle.util.Arrays; -import docking.DialogComponentProvider; -import docking.DockingWindowManager; -import docking.action.DockingAction; +import docking.*; +import docking.action.*; import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; import docking.widgets.OptionDialog; import docking.widgets.PasswordChangeDialog; import docking.widgets.table.GFilterTable; import docking.widgets.table.GTable; import generic.theme.GIcon; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.query.*; +import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.FunctionDatabase.Error; import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory; import ghidra.framework.plugintool.PluginTool; @@ -42,15 +46,14 @@ import resources.Icons; */ public class BSimServerDialog extends DialogComponentProvider { - // TODO: Add connected status indicator (not sure how this relates to elastic case which will likely have a Session concept) - // TODO: Add "Disconnect" action (only works when active connections is 0; does not apply to elastic) - private PluginTool tool; private BSimServerManager serverManager; private BSimServerTableModel serverTableModel; - private GFilterTable filterTable; + private GFilterTable serverTable; private BSimServerInfo lastAdded = null; + private ToggleDockingAction dbConnectionAction; + public BSimServerDialog(PluginTool tool, BSimServerManager serverManager) { super("BSim Server Manager"); this.tool = tool; @@ -60,7 +63,7 @@ public class BSimServerDialog extends DialogComponentProvider { addDismissButton(); setPreferredSize(600, 400); notifyContextChanged(); // kick actions to initialized enabled state - setHelpLocation(new HelpLocation("BSimSearchPlugin","BSim_Servers_Dialog" )); + setHelpLocation(new HelpLocation("BSimSearchPlugin", "BSim_Servers_Dialog")); } @Override @@ -70,34 +73,101 @@ public class BSimServerDialog extends DialogComponentProvider { } private void createToolbarActions() { - HelpLocation help = new HelpLocation("BSimSearchPlugin","Manage_Servers_Actions" ); - + HelpLocation help = new HelpLocation("BSimSearchPlugin", "Manage_Servers_Actions"); + DockingAction addServerAction = - new ActionBuilder("Add Server", "Dialog").toolBarIcon(Icons.ADD_ICON) - .helpLocation(help) - .onAction(e -> defineBsimServer()) - .build(); + new ActionBuilder("Add BSim Database", "Dialog").toolBarIcon(Icons.ADD_ICON) + .helpLocation(help) + .onAction(e -> defineBsimServer()) + .build(); addAction(addServerAction); DockingAction removeServerAction = - new ActionBuilder("Delete Server", "Dialog").toolBarIcon(Icons.DELETE_ICON) - .helpLocation(help) - .onAction(e -> deleteBsimServer()) - .enabledWhen(c -> hasSelection()) - .build(); + new ActionBuilder("Delete BSim Database", "Dialog").toolBarIcon(Icons.DELETE_ICON) + .helpLocation(help) + .onAction(e -> deleteBsimServer()) + .enabledWhen(c -> hasSelection()) + .build(); addAction(removeServerAction); - DockingAction changePasswordAction = new ActionBuilder("Change User Password", "Dialog") - .helpLocation(help) - .toolBarIcon(new GIcon("icon.bsim.change.password")) - .onAction(e -> changePassword()) - .enabledWhen(c -> hasSelection()) - .build(); + dbConnectionAction = + new ToggleActionBuilder("Toggle Database Connection", "Dialog").helpLocation(help) + .toolBarIcon(new GIcon("icon.bsim.disconnected")) + .onAction(e -> toggleSelectedJDBCDataSourceConnection()) + .enabledWhen(c -> isNonActiveJDBCDataSourceSelected(c)) + .build(); + addAction(dbConnectionAction); + + DockingAction changePasswordAction = + new ActionBuilder("Change User Password", "Dialog").helpLocation(help) + .toolBarIcon(new GIcon("icon.bsim.change.password")) + .onAction(e -> changePassword()) + .enabledWhen(c -> canChangePassword()) + .build(); addAction(changePasswordAction); } + private void toggleSelectedJDBCDataSourceConnection() { + + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); + if (serverInfo == null || serverInfo.getDBType() == DBType.elastic) { + return; + } + + BSimJDBCDataSource dataSource = BSimServerManager.getDataSourceIfExists(serverInfo); + if (dataSource == null) { + // connect + dataSource = BSimServerManager.getDataSource(serverInfo); + try (Connection connection = dataSource.getConnection()) { + // do nothing + } + catch (SQLException e) { + Msg.showError(this, rootPanel, "BSim Connection Failure", e.getMessage()); + } + } + else { + dataSource.dispose(); + } + serverTableModel.fireTableDataChanged(); + notifyContextChanged(); + } + + private boolean isNonActiveJDBCDataSourceSelected(ActionContext c) { + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); + if (serverInfo == null) { + return false; + } + + // TODO: May need connection listener on dataSource to facilitate GUI update, + // although modal dialog avoids the issue somewhat + + dbConnectionAction.setDescription(dbConnectionAction.getName()); + + ConnectionPoolStatus status = serverTableModel.getConnectionPoolStatus(serverInfo); + if (status.isActive) { + + // Show connected icon + dbConnectionAction + .setToolBarData(new ToolBarData(new GIcon("icon.bsim.connected"), null)); + dbConnectionAction.setSelected(true); + dbConnectionAction.setDescription("Disconnect idle BSim Database connection"); + + // disconnect permitted when no active connections + return status.activeCount == 0; + } + + // Show disconnected icon (elastic always shown as disconnected) + dbConnectionAction + .setToolBarData(new ToolBarData(new GIcon("icon.bsim.disconnected"), null)); + dbConnectionAction.setSelected(false); + dbConnectionAction.setDescription("Connect BSim Database"); + + // Action never enabled for elastic DB (i.e., does not use pooled JDBC data source) + return serverInfo.getDBType() != DBType.elastic; + } + private void changePassword() { - BSimServerInfo serverInfo = filterTable.getSelectedRowObject(); + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); if (serverInfo == null) { return; } @@ -141,8 +211,13 @@ public class BSimServerDialog extends DialogComponentProvider { } } + private boolean canChangePassword() { + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); + return serverInfo != null && serverInfo.getDBType() != DBType.file; + } + private void deleteBsimServer() { - BSimServerInfo selected = filterTable.getSelectedRowObject(); + BSimServerInfo selected = serverTable.getSelectedRowObject(); if (selected != null) { int answer = OptionDialog.showYesNoDialog(getComponent(), "Delete Server Configuration?", @@ -152,7 +227,7 @@ public class BSimServerDialog extends DialogComponentProvider { answer = OptionDialog.showOptionDialogWithCancelAsDefaultButton(getComponent(), "Active Server Configuration!", "Database connections are still active!\n" + - "Are you sure you want to delete server?", + "Are you sure you want to terminate connections and delete server?", "Yes", OptionDialog.WARNING_MESSAGE); if (answer == OptionDialog.YES_OPTION) { serverManager.removeServer(selected, true); @@ -169,7 +244,7 @@ public class BSimServerDialog extends DialogComponentProvider { if (newServerInfo != null) { serverManager.addServer(newServerInfo); lastAdded = newServerInfo; - Swing.runLater(() -> filterTable.setSelectedRowObject(newServerInfo)); + Swing.runLater(() -> serverTable.setSelectedRowObject(newServerInfo)); } } @@ -178,11 +253,11 @@ public class BSimServerDialog extends DialogComponentProvider { panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); serverTableModel = new BSimServerTableModel(serverManager); - filterTable = new GFilterTable<>(serverTableModel); - GTable table = filterTable.getTable(); + serverTable = new GFilterTable<>(serverTableModel); + GTable table = serverTable.getTable(); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.getSelectionModel().addListSelectionListener(e -> notifyContextChanged()); - panel.add(filterTable, BorderLayout.CENTER); + panel.add(serverTable, BorderLayout.CENTER); if (serverTableModel.getRowCount() > 0) { table.setRowSelectionInterval(0, 0); @@ -192,7 +267,7 @@ public class BSimServerDialog extends DialogComponentProvider { } private boolean hasSelection() { - return filterTable.getSelectedRowObject() != null; + return serverTable.getSelectedRowObject() != null; } public BSimServerInfo getLastAdded() { diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java index b26aeea431..8bed9cc704 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -16,27 +16,33 @@ package ghidra.features.bsim.gui.search.dialog; import java.awt.Component; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import javax.swing.Icon; import javax.swing.JLabel; import docking.widgets.table.*; import ghidra.docking.settings.Settings; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProviderStub; -import ghidra.program.model.listing.Program; import ghidra.util.table.column.AbstractGColumnRenderer; import ghidra.util.table.column.GColumnRenderer; -import ghidra.util.table.field.AbstractProgramBasedDynamicTableColumn; /** - * Table model for BSim database server definitions + * Table model for BSim database server definitions. + * + * NOTE: This implementation assumes modal dialog use and non-changing connection state + * while instance is in-use. This was done to avoid adding a conection listener which could + * introduce excessive overhead into the connection pool use. */ public class BSimServerTableModel extends GDynamicColumnTableModel { + private List servers; + private Map statusCache = new HashMap<>(); + private BSimServerManager serverManager; private BSimServerManagerListener listener = new BSimServerManagerListener() { @Override @@ -63,8 +69,18 @@ public class BSimServerTableModel extends GDynamicColumnTableModel new ConnectionPoolStatus(s)); } @Override @@ -74,7 +90,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + extends AbstractDynamicTableColumn { private GColumnRenderer renderer = new AbstractGColumnRenderer<>() { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { @@ -112,11 +128,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + private static class TypeColumn + extends AbstractDynamicTableColumn { + + @Override + public String getColumnName() { + return "Type"; + } + + @Override + public String getValue(BSimServerInfo serverInfo, Settings settings, Object data, + ServiceProvider provider) throws IllegalArgumentException { + + return serverInfo.getDBType().toString(); + } + + @Override + public int getColumnPreferredWidth() { + return 80; + } + } + + private static class HostColumn + extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -140,8 +174,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + private static class PortColumn + extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -161,64 +195,78 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + private static class ConnectionStatusColumnRenderer + extends AbstractGColumnRenderer { + + private static final ConnectionStatusColumnRenderer INSTANCE = + new ConnectionStatusColumnRenderer(); @Override - public String getColumnName() { - return "Active Connections"; - } + public Component getTableCellRendererComponent(GTableCellRenderingData data) { - @Override - public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data, - ServiceProvider provider) throws IllegalArgumentException { - int activeConnections = BSimServerManager.getActiveConnections(serverInfo); - if (activeConnections < 0) { - return null; + JLabel c = (JLabel) super.getTableCellRendererComponent(data); + + ConnectionPoolStatus status = (ConnectionPoolStatus) data.getValue(); + + // NOTE: Custom column renderer has neem established with future use of + // status icon in mind (e.g., H2 mixed-mode server enabled) + + Icon icon = null; // NOTE: may need default filler icon + String text = null; + if (status.isActive) { + text = Integer.toString(status.activeCount) + " / " + + Integer.toString(status.idleCount); } - return activeConnections; + c.setText(text); + c.setIcon(icon); + return c; } @Override - public int getColumnPreferredWidth() { - return 80; + public String getFilterString(ConnectionPoolStatus t, Settings settings) { + return null; // Filtering not supported } + } - private class TypeColumn - extends AbstractProgramBasedDynamicTableColumn { + private class ConnectionStatusColumn + extends AbstractDynamicTableColumn { @Override public String getColumnName() { - return "Type"; + return "Active/Idle Connections"; } @Override - public String getValue(BSimServerInfo serverInfo, Settings settings, Program data, - ServiceProvider provider) throws IllegalArgumentException { - - return serverInfo.getDBType().toString(); + public ConnectionPoolStatus getValue(BSimServerInfo serverInfo, Settings settings, + Object data, ServiceProvider provider) throws IllegalArgumentException { + return getConnectionPoolStatus(serverInfo); } @Override public int getColumnPreferredWidth() { - return 80; + return 150; + } + + @Override + public GColumnRenderer getColumnRenderer() { + return ConnectionStatusColumnRenderer.INSTANCE; } } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java new file mode 100644 index 0000000000..dc9d7b2dfe --- /dev/null +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.features.bsim.gui.search.dialog; + +import ghidra.features.bsim.gui.BSimServerManager; +import ghidra.features.bsim.query.BSimJDBCDataSource; +import ghidra.features.bsim.query.BSimServerInfo; + +class ConnectionPoolStatus { + BSimServerInfo serverInfo; + + final boolean isActive; + final int activeCount; + final int idleCount; + + ConnectionPoolStatus(BSimServerInfo serverInfo) { + this.serverInfo = serverInfo; + + BSimJDBCDataSource dataSource = BSimServerManager.getDataSourceIfExists(serverInfo); + if (dataSource == null) { + isActive = false; + activeCount = 0; + idleCount = 0; + } + else { + isActive = true; + activeCount = dataSource.getActiveConnections(); + idleCount = dataSource.getIdleConnections(); + } + } +} diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java index badb9638b2..f31c59cd4d 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java @@ -4,9 +4,9 @@ * 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. @@ -91,8 +91,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider { public boolean acceptServer(BSimServerInfo serverInfo) { // FIXME: Use task to correct dialog parenting issue caused by password prompt String errorMessage = null; - try { - FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true); + try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) { if (database.initialize()) { return true; } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java index 7b207d34ee..291e47316c 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java @@ -4,9 +4,9 @@ * 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. @@ -118,7 +118,10 @@ public class BSimClientFactory { } /** - * Given the URL for a BSim server construct the appropriate BSim client object (implementing FunctionDatabase) + * Given the URL for a BSim server construct the appropriate BSim client object + * (implementing FunctionDatabase). Returned instance must be + * {@link FunctionDatabase#close() closed} when done using it to prevent depletion + * of database connections. * @param bsimServerInfo BSim server details * @param async true if database commits should be asynchronous * @return the database client diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java index c0ca147904..5e82082b90 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java @@ -4,9 +4,9 @@ * 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. @@ -48,4 +48,15 @@ public interface BSimJDBCDataSource { */ int getActiveConnections(); + /** + * Get the number of idle connections in the associated connection pool + * @return number of idle connections + */ + int getIdleConnections(); + + /** + * Dispose pooled datasource. + */ + void dispose(); + } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java index c0dafe4370..70aea9aa2b 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java @@ -4,9 +4,9 @@ * 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. @@ -41,7 +41,8 @@ public class BSimPostgresDBConnectionManager { private static HashMap dataSourceMap = new HashMap<>(); - public static BSimPostgresDataSource getDataSource(BSimServerInfo postgresServerInfo) { + public static synchronized BSimPostgresDataSource getDataSource( + BSimServerInfo postgresServerInfo) { if (postgresServerInfo.getDBType() != DBType.postgres) { throw new IllegalArgumentException("expected postgres server info"); } @@ -54,19 +55,20 @@ public class BSimPostgresDBConnectionManager { return getDataSource(new BSimServerInfo(postgresUrl)); } - public static BSimPostgresDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { + public static synchronized BSimPostgresDataSource getDataSourceIfExists( + BSimServerInfo serverInfo) { return dataSourceMap.get(serverInfo); } - private static synchronized void remove(BSimServerInfo serverInfo) { + private static synchronized void remove(BSimServerInfo serverInfo, boolean force) { BSimPostgresDataSource ds = dataSourceMap.get(serverInfo); if (ds == null) { return; } int n = ds.bds.getNumActive(); - if (n != 0) { - System.out - .println("Unable to remove data source which has " + n + " active connections"); + if (n != 0 && !force) { + Msg.error(BSimPostgresDBConnectionManager.class, + "Unable to remove data source which has " + n + " active connections"); return; } ds.close(); @@ -113,8 +115,9 @@ public class BSimPostgresDBConnectionManager { bds.setUsername(userName); } + @Override public void dispose() { - remove(serverInfo); + remove(serverInfo, true); } private void close() { @@ -143,6 +146,11 @@ public class BSimPostgresDBConnectionManager { return bds.getNumActive(); } + @Override + public int getIdleConnections() { + return bds.getNumIdle(); + } + /** * Update password on {@link BasicDataSource} for use with future connect attempts. * Has no affect if username does not match username on data source. diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java index 4a85e5e54d..6998da5447 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java @@ -4,9 +4,9 @@ * 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. @@ -33,7 +33,9 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo; import ghidra.features.bsim.query.facade.SFQueryInfo; import ghidra.features.bsim.query.protocol.*; import ghidra.framework.Application; +import ghidra.program.model.data.DataUtilities; import ghidra.util.Msg; +import ghidra.util.StringUtilities; public interface FunctionDatabase extends AutoCloseable { @@ -239,7 +241,15 @@ public interface FunctionDatabase extends AutoCloseable { if (res == 3) { throw new LSHException("Query signature data has no setting information"); } - throw new LSHException("Query signature data does not match database"); + throw new LSHException("Query signature data " + + getFormattedVersion(manage.getMajorVersion(), manage.getMinorVersion(), + manage.getSettings()) + + " does not match database " + + getFormattedVersion(info.major, info.minor, info.settings)); + } + + private static String getFormattedVersion(int maj, int min, int settings) { + return String.format("%d.%d:0x%02x", maj, min, settings); } public static boolean checkSettingsForInsert(DescriptionManager manage, @@ -262,8 +272,11 @@ public interface FunctionDatabase extends AutoCloseable { if (res == 3) { throw new LSHException("Trying to insert signature data with no setting information"); } - throw new LSHException( - "Trying to insert signature data with settings that don't match database"); + throw new LSHException("Trying to insert signature data " + + getFormattedVersion(manage.getMajorVersion(), manage.getMinorVersion(), + manage.getSettings()) + + " with settings that don't match database " + + getFormattedVersion(info.major, info.minor, info.settings)); } public static String constructFatalError(int flags, ExecutableRecord newrec, diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java index a5d13af20f..b7ad1d0629 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java @@ -4,9 +4,9 @@ * 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. @@ -28,6 +28,7 @@ import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.FunctionDatabase.ConnectionType; import ghidra.features.bsim.query.FunctionDatabase.Status; +import ghidra.util.Msg; public class BSimH2FileDBConnectionManager { @@ -44,7 +45,7 @@ public class BSimH2FileDBConnectionManager { * Get all H2 File DB data sorces which exist in the JVM. * @return all H2 File DB data sorces */ - public static Collection getAllDataSources() { + public static synchronized Collection getAllDataSources() { // Create copy to avoid potential concurrent modification return Collections.unmodifiableCollection(new ArrayList<>(dataSourceMap.values())); } @@ -57,7 +58,7 @@ public class BSimH2FileDBConnectionManager { * @throws IllegalArgumentException if {@code fileServerInfo} does not specify an * H2 File DB type. */ - public static BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) { + public static synchronized BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) { if (fileServerInfo.getDBType() != DBType.file) { throw new IllegalArgumentException("expected file info"); } @@ -79,26 +80,26 @@ public class BSimH2FileDBConnectionManager { * @return existing H2 File data source or null if server info does not correspond to an * H2 File or has not be established as an H2 File data source. */ - public static BSimH2FileDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { + public static synchronized BSimH2FileDataSource getDataSourceIfExists( + BSimServerInfo serverInfo) { return dataSourceMap.get(serverInfo); } - private static synchronized void remove(BSimServerInfo serverInfo, boolean force) { + private static synchronized boolean remove(BSimServerInfo serverInfo, boolean force) { BSimH2FileDataSource ds = dataSourceMap.get(serverInfo); if (ds == null) { - return; + return true; } int n = ds.bds.getNumActive(); - if (n != 0) { - System.out - .println("Unable to remove data source which has " + n + " active connections"); - if (!force) { - return; - } + if (n != 0 && !force) { + Msg.error(BSimH2FileDBConnectionManager.class, + "Unable to remove data source which has " + n + " active connections"); + return false; } ds.close(); dataSourceMap.remove(serverInfo); BSimVectorStoreManager.remove(serverInfo); + return true; } /** @@ -123,20 +124,31 @@ public class BSimH2FileDBConnectionManager { return serverInfo; } + @Override public void dispose() { BSimH2FileDBConnectionManager.remove(serverInfo, true); } /** - * Delete the database files associated with this H2 File DB. When complete - * this data source will no longer be valid and should no tbe used. + * Delete the database files associated with this H2 File DB. This will fail immediately + * if active connections exist. Otherwise removal will be attempted and this data source + * will no longer be valid. + * @return true if DB sucessfully removed */ - public void delete() { - dispose(); + public synchronized boolean delete() { File dbf = new File(serverInfo.getDBName()); - // TODO: Should we check for lock on database - could be another process + if (getActiveConnections() != 0) { + Msg.error(this, "Failed to delete active database: " + dbf); + return false; + } + + dispose(); + + if (dbf.isFile()) { + return true; + } String name = dbf.getName(); int ix = name.lastIndexOf(BSimServerInfo.H2_FILE_EXTENSION); @@ -145,6 +157,13 @@ public class BSimH2FileDBConnectionManager { } DeleteDbFiles.execute(dbf.getParent(), name, true); + + if (!dbf.isFile()) { + return true; + } + + Msg.error(this, "Failed to delete database: " + dbf); + return false; } /** @@ -181,6 +200,11 @@ public class BSimH2FileDBConnectionManager { return bds.getNumActive(); } + @Override + public int getIdleConnections() { + return bds.getNumIdle(); + } + private String getH2FileUrl() { // Remove H2 db file extension if present diff --git a/Ghidra/Features/BSim/src/main/resources/images/connect.png b/Ghidra/Features/BSim/src/main/resources/images/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..024138eb33b9124af6db8149747adbb41c1b8cfc GIT binary patch literal 748 zcmVzR>QH2KN`fNj zBHaWZEgiB#>49kYe(borqx+hj`JS_1d)R}7LjHa+tu+qg(TC+eO4&q(D;ZL8C8o8; zLEfZxiIlQ~iE1b1qBZ2=VhsA0V`*@y@M|Sc2@Wtadv3g0hOc*O(ADVFjPTWAYz(JXS z8MBaa%aCO{h8lvp*ONKIh5Bg|-DNo^;jg=q=uDZ@vodOJXfu;GK`ze`H-VrWK!nUg zje)ufRUJ&ouB2nYB2_pI=gji&GcuBL=+DM-wBZ)fbc7&a9AP1Ztk5)S4Ah03bqXOt zxx}VdK|CIVljyc=oqO1G{;Qew7T~%?tSw{^mi$E(2^Td6>M8+m4H!qrBtj~%w(Y~Q zVrXkVOEN#2&@(U(cX3H&S97B(;-}{)?<>?0)RjVZqrJ&ONChgCgE9%PC}CSBp!+d1 z`bkAqacXYz!94aLsJZ!K=3b*?T#ge1nL>bsZNf4&8mt(EkXYZ?MK0YwH2ZOI9{(VN z&%WPH*v6AY+yG?)mZ`CxE@5ZK2lW}4Pr-ctMRE2V`yf8$PurT4&^p3q*2kt>M8OM& zl@MbQ=U&8BS_$dSjo(q&2Pyd!8`}{~Xr#A_DDL|G({Ha$;Xe@?&|@q4QbvV}D#ixB ey}zEqA^Zgf(1+rQ>#k7%0000h5&w{Y-QlBkdy7eSyz8|k(w=syt3MbOGZFmTy2f|dnI3kj6Sz)H!` z%1hM3FiovS8#Qs98Rxs5^PSs#9S4da6*};44(Iv3@B5rb3BwQ$Is?0$0ilY#Q^EELL=6SfS*Ly93GR<|lPYvfPXoM^)HN1)!-B@N5Zi(e|Ge`rl{XLurIiz-D}wH selectedFunctions = new HashSet<>(); private BSimFilterPanel filterPanel; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -57,6 +56,7 @@ public class BSimFilterPanelTest extends AbstractBSimPluginTest { filterPanel = BSimSearchDialogTestHelper.getFilterPanel(searchDialog); } + @Override @After public void tearDown() throws Exception { close(searchDialog); diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java index 4792197296..ac1cb0ef01 100644 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java @@ -4,9 +4,9 @@ * 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. @@ -17,8 +17,7 @@ package ghidra.features.bsim.gui.search.dialog; import java.util.Set; -import ghidra.features.bsim.gui.BSimSearchPlugin; -import ghidra.features.bsim.gui.BSimSearchPluginTestHelper; +import ghidra.features.bsim.gui.*; import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.FunctionDatabase; import ghidra.features.bsim.query.facade.TestBSimServerInfo; diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/inmemory/BSimH2DatabaseManagerTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java similarity index 98% rename from Ghidra/Features/BSim/src/test.slow/java/ghidra/query/inmemory/BSimH2DatabaseManagerTest.java rename to Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java index 3878d4caa6..f3dd96c173 100644 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/inmemory/BSimH2DatabaseManagerTest.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java @@ -4,16 +4,16 @@ * 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 ghidra.query.inmemory; +package ghidra.features.bsim.query.file; import static org.junit.Assert.*; @@ -26,7 +26,6 @@ import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.FunctionDatabase.Error; import ghidra.features.bsim.query.description.DatabaseInformation; -import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.protocol.CreateDatabase; import ghidra.features.bsim.query.protocol.ResponseInfo; @@ -50,7 +49,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe @After public void tearDown() { - //cleanup(); + cleanup(); } private File getTempDbDir() { @@ -77,7 +76,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe } private BSimServerInfo createDatabase(String databaseName, List tags, - List execats, String expectedError) { + List execats, String expectedError) { BSimServerInfo h2DbInfo = getBsimServerInfo(databaseName); Msg.debug(this, "Creating H2 File DB: " + h2DbInfo); diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java similarity index 99% rename from Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTest.java rename to Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java index b1073152bf..07154c507b 100755 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTest.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java @@ -4,16 +4,16 @@ * 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 ghidra.query.test; +package ghidra.features.bsim.query.test; import static org.junit.Assert.*; diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTestUtil.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTestUtil.java similarity index 99% rename from Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTestUtil.java rename to Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTestUtil.java index 7a1e2f1728..d1ffa67c49 100755 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTestUtil.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTestUtil.java @@ -4,16 +4,16 @@ * 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 ghidra.query.test; +package ghidra.features.bsim.query.test; import java.io.*;