GP-4867 Added BSim Server connection toggle for H2 and Postgres. Fixed various related bugs.

This commit is contained in:
ghidra1 2024-08-28 15:46:47 -04:00
parent 6a94f1f7ab
commit 249d91f0a1
31 changed files with 530 additions and 255 deletions

View file

@ -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/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png||GHIDRA||||END|
src/main/resources/bsim.log4j.xml||GHIDRA||||END| src/main/resources/bsim.log4j.xml||GHIDRA||||END|
src/main/resources/images/checkmark_yellow.gif||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/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-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| 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|

View file

@ -14,4 +14,7 @@ icon.bsim.results.status.ignored = checkmark_yellow.gif
icon.bsim.functions.table = FunctionScope.gif icon.bsim.functions.table = FunctionScope.gif
icon.bsim.connected = connect.png
icon.bsim.disconnected = disconnect.png
[Dark Defaults] [Dark Defaults]

View file

@ -71,22 +71,17 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
askValues("Select Database File", null, values); askValues("Select Database File", null, values);
File h2DbFile = values.getFile(DATABASE); File h2DbFile = values.getFile(DATABASE);
BSimServerInfo serverInfo =
new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath());
FunctionDatabase h2Database = null; BSimH2FileDataSource existingBDS =
try { BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
BSimServerInfo serverInfo = if (existingBDS != null && existingBDS.getActiveConnections() > 0) {
new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath()); popup("There is an existing connection to the database.");
h2Database = BSimClientFactory.buildClient(serverInfo, false); return;
BSimH2FileDataSource bds = }
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo());
if (bds == null) { try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) {
popup(h2DbFile.getAbsolutePath() + " is not an H2 database file");
return;
}
if (bds.getActiveConnections() > 0) {
popup("There is an existing connection to the database.");
return;
}
h2Database.initialize(); h2Database.initialize();
DatabaseInformation dbInfo = h2Database.getInfo(); DatabaseInformation dbInfo = h2Database.getInfo();
@ -169,11 +164,13 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
} }
finally { finally {
if (h2Database != null) { if (existingBDS == null) {
h2Database.close(); // Dispose database source if it did not previously exist
BSimH2FileDataSource bds = BSimH2FileDataSource bds =
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
bds.dispose(); if (bds != null) {
bds.dispose();
}
} }
} }
} }

View file

@ -31,7 +31,6 @@ import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
import ghidra.features.bsim.query.protocol.*; import ghidra.features.bsim.query.protocol.*;
import ghidra.util.MessageType; import ghidra.util.MessageType;
import ghidra.util.Msg;
public class CreateH2BSimDatabaseScript extends GhidraScript { public class CreateH2BSimDatabaseScript extends GhidraScript {
private static final String NAME = "Database Name"; private static final String NAME = "Database Name";
@ -80,31 +79,27 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
askValues("Enter Database Parameters", askValues("Enter Database Parameters",
"Enter values required to create a new BSim H2 database.", values); "Enter values required to create a new BSim H2 database.", values);
FunctionDatabase h2Database = null; String databaseName = values.getString(NAME);
try { File dbDir = values.getFile(DIRECTORY);
String databaseName = values.getString(NAME); String template = values.getChoice(DATABASE_TEMPLATE);
File dbDir = values.getFile(DIRECTORY); String functionTagsCSV = values.getString(FUNCTION_TAGS);
String template = values.getChoice(DATABASE_TEMPLATE); List<String> tags = parseCSV(functionTagsCSV);
String functionTagsCSV = values.getString(FUNCTION_TAGS);
List<String> tags = parseCSV(functionTagsCSV);
String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES); String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES);
List<String> cats = parseCSV(exeCatCSV); List<String> 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 = BSimH2FileDataSource existingBDS =
new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath()); BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
h2Database = BSimClientFactory.buildClient(serverInfo, false); if (existingBDS != null && existingBDS.getActiveConnections() > 0) {
BSimH2FileDataSource bds = popup("There is an existing connection to the database.");
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); return;
if (bds.getActiveConnections() > 0) { }
//if this happens, there is a connection to the database but the
//database file was deleted try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) {
Msg.showError(this, null, "Connection Error",
"There is an existing connection to the database!");
return;
}
CreateDatabase command = new CreateDatabase(); CreateDatabase command = new CreateDatabase();
command.info = new DatabaseInformation(); command.info = new DatabaseInformation();
@ -140,11 +135,13 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
popup("Database " + values.getString(NAME) + " created successfully!"); popup("Database " + values.getString(NAME) + " created successfully!");
} }
finally { finally {
if (h2Database != null) { if (existingBDS == null) {
h2Database.close(); // Dispose database source if it did not previously exist
BSimH2FileDataSource bds = BSimH2FileDataSource bds =
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
bds.dispose(); if (bds != null) {
bds.dispose();
}
} }
} }

View file

@ -57,18 +57,24 @@
entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip 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.</P> and port (if applicable), and finally the number of active connections.</P>
<P>There are three primary actions for this dialog:</P> <P>There are four primary actions for this dialog:</P>
<A name="Manage_Servers_Actions"></A> <A name="Manage_Servers_Actions"></A>
<UL> <UL>
<LI><IMG alt="" src="Icons.ADD_ICON">&nbsp;Add a new database/server definition - a <LI><IMG alt="" src="Icons.ADD_ICON">&nbsp;Add a new BSim database/server definition - an
Define Server Dialog will be shown.</LI> Add BSim Server Dialog will be shown.</LI>
<LI><IMG alt="" src="Icons.DELETE_ICON">&nbsp;Delete a database/server definition - The <LI><IMG alt="" src="Icons.DELETE_ICON">&nbsp;Delete a database/server definition - The
selected entry will be deleted.</LI> selected entry will be deleted. This action will force an immediate disconnect for an
active/idle connection and should be used with care.</LI>
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">&nbsp;
Connect or disconnect an inactive database/server connection. This action is not supported
by Elastic database servers.</LI>
<LI><IMG alt="" src="icon.bsim.change.password">&nbsp;Change password - A change password <LI><IMG alt="" src="icon.bsim.change.password">&nbsp;Change password - A change password
dialog will appear for the selected entry</LI> 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).</LI>
</UL> </UL>
<H3><A name="Add_Server_Definition_Dialog">Defining a new BSim server/database</A></H3> <H3><A name="Add_Server_Definition_Dialog">Defining a new BSim server/database</A></H3>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -72,7 +72,7 @@ public class BSimSearchPlugin extends ProgramPlugin {
private Set<BSimSearchResultsProvider> searchResultsProviders = new HashSet<>(); private Set<BSimSearchResultsProvider> searchResultsProviders = new HashSet<>();
private Set<BSimOverviewProvider> overviewProviders = new HashSet<>(); private Set<BSimOverviewProvider> overviewProviders = new HashSet<>();
private BSimServerManager serverManager = new BSimServerManager(); private BSimServerManager serverManager = BSimServerManager.getBSimServerManager();
private BSimSearchService searchService; private BSimSearchService searchService;
private BSimServerCache lastUsedServerCache = null; private BSimServerCache lastUsedServerCache = null;

View file

@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.features.bsim.gui.search.dialog; package ghidra.features.bsim.gui;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList; 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.BSimPostgresDBConnectionManager.BSimPostgresDataSource;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
@ -36,12 +36,24 @@ import ghidra.util.Swing;
* Managers BSim database server definitions and connections * Managers BSim database server definitions and connections
*/ */
public class BSimServerManager { 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<BSimServerInfo> serverInfos = new HashSet<>(); private Set<BSimServerInfo> serverInfos = new HashSet<>();
private List<BSimServerManagerListener> listeners = new CopyOnWriteArrayList<>(); private List<BSimServerManagerListener> listeners = new CopyOnWriteArrayList<>();
public BSimServerManager() { private BSimServerManager() {
List<File> files = Application.getUserSettingsFiles("bsim", ".server.properties"); List<File> files = Application.getUserSettingsFiles("bsim", ".server.properties");
for (File file : files) { for (File file : files) {
BSimServerInfo info = readBsimServerInfoFile(file); 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<BSimServerInfo> getServerInfos() { public Set<BSimServerInfo> getServerInfos() {
return new HashSet<>(serverInfos); return new HashSet<>(serverInfos);
} }
@ -108,6 +124,10 @@ public class BSimServerManager {
return serverFile.delete(); 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) { public void addServer(BSimServerInfo newServerInfo) {
if (saveBSimServerInfo(newServerInfo)) { if (saveBSimServerInfo(newServerInfo)) {
serverInfos.add(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(); DBType dbType = info.getDBType();
if (dbType == DBType.file) { if (dbType == DBType.file) {
BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSource(info); BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSourceIfExists(info);
int active = ds.getActiveConnections(); if (ds != null) {
if (active != 0) { int active = ds.getActiveConnections();
if (!force) { if (active != 0 && !force) {
return false; return false;
} }
ds.dispose(); ds.dispose();
} }
} }
else if (dbType == DBType.postgres) { else if (dbType == DBType.postgres) {
BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSource(info); BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSourceIfExists(info);
int active = ds.getActiveConnections(); if (ds != null) {
if (active != 0) { int active = ds.getActiveConnections();
if (!force) { if (active != 0 && !force) {
return false; return false;
} }
ds.dispose(); 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)) { if (serverInfos.remove(info)) {
removeServerFileFromSettings(info); removeServerFileFromSettings(info);
notifyServerListChanged(); 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()) { switch (serverInfo.getDBType()) {
case postgres: case postgres:
BSimPostgresDataSource postgresDs = return BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
if (postgresDs != null) {
return postgresDs.getActiveConnections();
}
break;
case file: case file:
BSimH2FileDataSource h2FileDs = return BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
if (h2FileDs != null) {
return h2FileDs.getActiveConnections();
}
break;
default: 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;
} }
} }

View file

@ -30,6 +30,7 @@ import docking.widgets.EmptyBorderButton;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.textfield.FloatingPointTextField; import docking.widgets.textfield.FloatingPointTextField;
import generic.theme.Gui; import generic.theme.Gui;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.features.bsim.query.description.DatabaseInformation;
import ghidra.features.bsim.query.facade.QueryDatabaseException; import ghidra.features.bsim.query.facade.QueryDatabaseException;

View file

@ -16,6 +16,7 @@
package ghidra.features.bsim.gui.search.dialog; package ghidra.features.bsim.gui.search.dialog;
import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;

View file

@ -30,6 +30,7 @@ import docking.widgets.textfield.IntegerTextField;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.gui.filters.BSimFilterType; import ghidra.features.bsim.gui.filters.BSimFilterType;
import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.features.bsim.query.description.DatabaseInformation;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;

View file

@ -16,21 +16,25 @@
package ghidra.features.bsim.gui.search.dialog; package ghidra.features.bsim.gui.search.dialog;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.sql.Connection;
import java.sql.SQLException;
import javax.swing.*; import javax.swing.*;
import org.bouncycastle.util.Arrays; import org.bouncycastle.util.Arrays;
import docking.DialogComponentProvider; import docking.*;
import docking.DockingWindowManager; import docking.action.*;
import docking.action.DockingAction;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.PasswordChangeDialog; import docking.widgets.PasswordChangeDialog;
import docking.widgets.table.GFilterTable; import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.*; 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.Error;
import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory; import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -42,15 +46,14 @@ import resources.Icons;
*/ */
public class BSimServerDialog extends DialogComponentProvider { 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 PluginTool tool;
private BSimServerManager serverManager; private BSimServerManager serverManager;
private BSimServerTableModel serverTableModel; private BSimServerTableModel serverTableModel;
private GFilterTable<BSimServerInfo> filterTable; private GFilterTable<BSimServerInfo> serverTable;
private BSimServerInfo lastAdded = null; private BSimServerInfo lastAdded = null;
private ToggleDockingAction dbConnectionAction;
public BSimServerDialog(PluginTool tool, BSimServerManager serverManager) { public BSimServerDialog(PluginTool tool, BSimServerManager serverManager) {
super("BSim Server Manager"); super("BSim Server Manager");
this.tool = tool; this.tool = tool;
@ -60,7 +63,7 @@ public class BSimServerDialog extends DialogComponentProvider {
addDismissButton(); addDismissButton();
setPreferredSize(600, 400); setPreferredSize(600, 400);
notifyContextChanged(); // kick actions to initialized enabled state notifyContextChanged(); // kick actions to initialized enabled state
setHelpLocation(new HelpLocation("BSimSearchPlugin","BSim_Servers_Dialog" )); setHelpLocation(new HelpLocation("BSimSearchPlugin", "BSim_Servers_Dialog"));
} }
@Override @Override
@ -70,34 +73,101 @@ public class BSimServerDialog extends DialogComponentProvider {
} }
private void createToolbarActions() { private void createToolbarActions() {
HelpLocation help = new HelpLocation("BSimSearchPlugin","Manage_Servers_Actions" ); HelpLocation help = new HelpLocation("BSimSearchPlugin", "Manage_Servers_Actions");
DockingAction addServerAction = DockingAction addServerAction =
new ActionBuilder("Add Server", "Dialog").toolBarIcon(Icons.ADD_ICON) new ActionBuilder("Add BSim Database", "Dialog").toolBarIcon(Icons.ADD_ICON)
.helpLocation(help) .helpLocation(help)
.onAction(e -> defineBsimServer()) .onAction(e -> defineBsimServer())
.build(); .build();
addAction(addServerAction); addAction(addServerAction);
DockingAction removeServerAction = DockingAction removeServerAction =
new ActionBuilder("Delete Server", "Dialog").toolBarIcon(Icons.DELETE_ICON) new ActionBuilder("Delete BSim Database", "Dialog").toolBarIcon(Icons.DELETE_ICON)
.helpLocation(help) .helpLocation(help)
.onAction(e -> deleteBsimServer()) .onAction(e -> deleteBsimServer())
.enabledWhen(c -> hasSelection()) .enabledWhen(c -> hasSelection())
.build(); .build();
addAction(removeServerAction); addAction(removeServerAction);
DockingAction changePasswordAction = new ActionBuilder("Change User Password", "Dialog") dbConnectionAction =
.helpLocation(help) new ToggleActionBuilder("Toggle Database Connection", "Dialog").helpLocation(help)
.toolBarIcon(new GIcon("icon.bsim.change.password")) .toolBarIcon(new GIcon("icon.bsim.disconnected"))
.onAction(e -> changePassword()) .onAction(e -> toggleSelectedJDBCDataSourceConnection())
.enabledWhen(c -> hasSelection()) .enabledWhen(c -> isNonActiveJDBCDataSourceSelected(c))
.build(); .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); 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() { private void changePassword() {
BSimServerInfo serverInfo = filterTable.getSelectedRowObject(); BSimServerInfo serverInfo = serverTable.getSelectedRowObject();
if (serverInfo == null) { if (serverInfo == null) {
return; 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() { private void deleteBsimServer() {
BSimServerInfo selected = filterTable.getSelectedRowObject(); BSimServerInfo selected = serverTable.getSelectedRowObject();
if (selected != null) { if (selected != null) {
int answer = int answer =
OptionDialog.showYesNoDialog(getComponent(), "Delete Server Configuration?", OptionDialog.showYesNoDialog(getComponent(), "Delete Server Configuration?",
@ -152,7 +227,7 @@ public class BSimServerDialog extends DialogComponentProvider {
answer = OptionDialog.showOptionDialogWithCancelAsDefaultButton(getComponent(), answer = OptionDialog.showOptionDialogWithCancelAsDefaultButton(getComponent(),
"Active Server Configuration!", "Active Server Configuration!",
"Database connections are still active!\n" + "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); "Yes", OptionDialog.WARNING_MESSAGE);
if (answer == OptionDialog.YES_OPTION) { if (answer == OptionDialog.YES_OPTION) {
serverManager.removeServer(selected, true); serverManager.removeServer(selected, true);
@ -169,7 +244,7 @@ public class BSimServerDialog extends DialogComponentProvider {
if (newServerInfo != null) { if (newServerInfo != null) {
serverManager.addServer(newServerInfo); serverManager.addServer(newServerInfo);
lastAdded = 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)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
serverTableModel = new BSimServerTableModel(serverManager); serverTableModel = new BSimServerTableModel(serverManager);
filterTable = new GFilterTable<>(serverTableModel); serverTable = new GFilterTable<>(serverTableModel);
GTable table = filterTable.getTable(); GTable table = serverTable.getTable();
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().addListSelectionListener(e -> notifyContextChanged()); table.getSelectionModel().addListSelectionListener(e -> notifyContextChanged());
panel.add(filterTable, BorderLayout.CENTER); panel.add(serverTable, BorderLayout.CENTER);
if (serverTableModel.getRowCount() > 0) { if (serverTableModel.getRowCount() > 0) {
table.setRowSelectionInterval(0, 0); table.setRowSelectionInterval(0, 0);
@ -192,7 +267,7 @@ public class BSimServerDialog extends DialogComponentProvider {
} }
private boolean hasSelection() { private boolean hasSelection() {
return filterTable.getSelectedRowObject() != null; return serverTable.getSelectedRowObject() != null;
} }
public BSimServerInfo getLastAdded() { public BSimServerInfo getLastAdded() {

View file

@ -16,27 +16,33 @@
package ghidra.features.bsim.gui.search.dialog; package ghidra.features.bsim.gui.search.dialog;
import java.awt.Component; import java.awt.Component;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JLabel; import javax.swing.JLabel;
import docking.widgets.table.*; import docking.widgets.table.*;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.ServiceProviderStub; import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.program.model.listing.Program;
import ghidra.util.table.column.AbstractGColumnRenderer; import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer; 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<BSimServerInfo, Object> { public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInfo, Object> {
private List<BSimServerInfo> servers; private List<BSimServerInfo> servers;
private Map<BSimServerInfo, ConnectionPoolStatus> statusCache = new HashMap<>();
private BSimServerManager serverManager; private BSimServerManager serverManager;
private BSimServerManagerListener listener = new BSimServerManagerListener() { private BSimServerManagerListener listener = new BSimServerManagerListener() {
@Override @Override
@ -63,8 +69,18 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
@Override @Override
public boolean isSortable(int columnIndex) { public void fireTableDataChanged() {
return columnIndex != 0; statusCache.clear();
super.fireTableDataChanged();
}
/**
* Get DB connection pool status for a specified server
* @param serverInfo server info
* @return connection pool status
*/
ConnectionPoolStatus getConnectionPoolStatus(BSimServerInfo serverInfo) {
return statusCache.computeIfAbsent(serverInfo, s -> new ConnectionPoolStatus(s));
} }
@Override @Override
@ -74,7 +90,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
descriptor.addVisibleColumn(new TypeColumn()); descriptor.addVisibleColumn(new TypeColumn());
descriptor.addVisibleColumn(new HostColumn()); descriptor.addVisibleColumn(new HostColumn());
descriptor.addVisibleColumn(new PortColumn()); descriptor.addVisibleColumn(new PortColumn());
descriptor.addVisibleColumn(new ActiveConnectionColumn()); descriptor.addVisibleColumn(new ConnectionStatusColumn());
return descriptor; return descriptor;
} }
@ -84,7 +100,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
private class DatabaseNameColumn private class DatabaseNameColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> { extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
private GColumnRenderer<String> renderer = new AbstractGColumnRenderer<>() { private GColumnRenderer<String> renderer = new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -112,11 +128,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
@Override @Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data, public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException { ServiceProvider provider) throws IllegalArgumentException {
// FIXME: Get cell tooltip to show full getDBName which includes file path
return serverInfo.getShortDBName(); return serverInfo.getShortDBName();
} }
@ -131,8 +144,29 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
} }
private class HostColumn private static class TypeColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> { extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@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<BSimServerInfo, String, Object> {
@Override @Override
public String getColumnName() { public String getColumnName() {
@ -140,8 +174,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
@Override @Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data, public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException { ServiceProvider provider) throws IllegalArgumentException {
return serverInfo.getServerName(); return serverInfo.getServerName();
} }
@ -152,8 +186,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
} }
private class PortColumn private static class PortColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, Integer> { extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@Override @Override
public String getColumnName() { public String getColumnName() {
@ -161,64 +195,78 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
} }
@Override @Override
public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data, public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException { ServiceProvider provider) throws IllegalArgumentException {
int port = serverInfo.getPort(); int port = serverInfo.getPort();
if (port <= 0) { if (port <= 0) {
return null; return null;
} }
return port; return Integer.toString(port);
} }
@Override @Override
public int getColumnPreferredWidth() { public int getColumnPreferredWidth() {
return 80; return 60;
} }
} }
private class ActiveConnectionColumn private static class ConnectionStatusColumnRenderer
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, Integer> { extends AbstractGColumnRenderer<ConnectionPoolStatus> {
private static final ConnectionStatusColumnRenderer INSTANCE =
new ConnectionStatusColumnRenderer();
@Override @Override
public String getColumnName() { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
return "Active Connections";
}
@Override JLabel c = (JLabel) super.getTableCellRendererComponent(data);
public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data,
ServiceProvider provider) throws IllegalArgumentException { ConnectionPoolStatus status = (ConnectionPoolStatus) data.getValue();
int activeConnections = BSimServerManager.getActiveConnections(serverInfo);
if (activeConnections < 0) { // NOTE: Custom column renderer has neem established with future use of
return null; // 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 @Override
public int getColumnPreferredWidth() { public String getFilterString(ConnectionPoolStatus t, Settings settings) {
return 80; return null; // Filtering not supported
} }
} }
private class TypeColumn private class ConnectionStatusColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> { extends AbstractDynamicTableColumn<BSimServerInfo, ConnectionPoolStatus, Object> {
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Type"; return "Active/Idle Connections";
} }
@Override @Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data, public ConnectionPoolStatus getValue(BSimServerInfo serverInfo, Settings settings,
ServiceProvider provider) throws IllegalArgumentException { Object data, ServiceProvider provider) throws IllegalArgumentException {
return getConnectionPoolStatus(serverInfo);
return serverInfo.getDBType().toString();
} }
@Override @Override
public int getColumnPreferredWidth() { public int getColumnPreferredWidth() {
return 80; return 150;
}
@Override
public GColumnRenderer<ConnectionPoolStatus> getColumnRenderer() {
return ConnectionStatusColumnRenderer.INSTANCE;
} }
} }

View file

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

View file

@ -91,8 +91,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
public boolean acceptServer(BSimServerInfo serverInfo) { public boolean acceptServer(BSimServerInfo serverInfo) {
// FIXME: Use task to correct dialog parenting issue caused by password prompt // FIXME: Use task to correct dialog parenting issue caused by password prompt
String errorMessage = null; String errorMessage = null;
try { try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) {
FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true);
if (database.initialize()) { if (database.initialize()) {
return true; return true;
} }

View file

@ -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 bsimServerInfo BSim server details
* @param async true if database commits should be asynchronous * @param async true if database commits should be asynchronous
* @return the database client * @return the database client

View file

@ -48,4 +48,15 @@ public interface BSimJDBCDataSource {
*/ */
int getActiveConnections(); 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();
} }

View file

@ -41,7 +41,8 @@ public class BSimPostgresDBConnectionManager {
private static HashMap<BSimServerInfo, BSimPostgresDataSource> dataSourceMap = new HashMap<>(); private static HashMap<BSimServerInfo, BSimPostgresDataSource> dataSourceMap = new HashMap<>();
public static BSimPostgresDataSource getDataSource(BSimServerInfo postgresServerInfo) { public static synchronized BSimPostgresDataSource getDataSource(
BSimServerInfo postgresServerInfo) {
if (postgresServerInfo.getDBType() != DBType.postgres) { if (postgresServerInfo.getDBType() != DBType.postgres) {
throw new IllegalArgumentException("expected postgres server info"); throw new IllegalArgumentException("expected postgres server info");
} }
@ -54,19 +55,20 @@ public class BSimPostgresDBConnectionManager {
return getDataSource(new BSimServerInfo(postgresUrl)); return getDataSource(new BSimServerInfo(postgresUrl));
} }
public static BSimPostgresDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { public static synchronized BSimPostgresDataSource getDataSourceIfExists(
BSimServerInfo serverInfo) {
return dataSourceMap.get(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); BSimPostgresDataSource ds = dataSourceMap.get(serverInfo);
if (ds == null) { if (ds == null) {
return; return;
} }
int n = ds.bds.getNumActive(); int n = ds.bds.getNumActive();
if (n != 0) { if (n != 0 && !force) {
System.out Msg.error(BSimPostgresDBConnectionManager.class,
.println("Unable to remove data source which has " + n + " active connections"); "Unable to remove data source which has " + n + " active connections");
return; return;
} }
ds.close(); ds.close();
@ -113,8 +115,9 @@ public class BSimPostgresDBConnectionManager {
bds.setUsername(userName); bds.setUsername(userName);
} }
@Override
public void dispose() { public void dispose() {
remove(serverInfo); remove(serverInfo, true);
} }
private void close() { private void close() {
@ -143,6 +146,11 @@ public class BSimPostgresDBConnectionManager {
return bds.getNumActive(); return bds.getNumActive();
} }
@Override
public int getIdleConnections() {
return bds.getNumIdle();
}
/** /**
* Update password on {@link BasicDataSource} for use with future connect attempts. * Update password on {@link BasicDataSource} for use with future connect attempts.
* Has no affect if username does not match username on data source. * Has no affect if username does not match username on data source.

View file

@ -33,7 +33,9 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo;
import ghidra.features.bsim.query.facade.SFQueryInfo; import ghidra.features.bsim.query.facade.SFQueryInfo;
import ghidra.features.bsim.query.protocol.*; import ghidra.features.bsim.query.protocol.*;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.program.model.data.DataUtilities;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.StringUtilities;
public interface FunctionDatabase extends AutoCloseable { public interface FunctionDatabase extends AutoCloseable {
@ -239,7 +241,15 @@ public interface FunctionDatabase extends AutoCloseable {
if (res == 3) { if (res == 3) {
throw new LSHException("Query signature data has no setting information"); 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, public static boolean checkSettingsForInsert(DescriptionManager manage,
@ -262,8 +272,11 @@ public interface FunctionDatabase extends AutoCloseable {
if (res == 3) { if (res == 3) {
throw new LSHException("Trying to insert signature data with no setting information"); throw new LSHException("Trying to insert signature data with no setting information");
} }
throw new LSHException( throw new LSHException("Trying to insert signature data " +
"Trying to insert signature data with settings that don't match database"); 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, public static String constructFatalError(int flags, ExecutableRecord newrec,

View file

@ -28,6 +28,7 @@ import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.ConnectionType; import ghidra.features.bsim.query.FunctionDatabase.ConnectionType;
import ghidra.features.bsim.query.FunctionDatabase.Status; import ghidra.features.bsim.query.FunctionDatabase.Status;
import ghidra.util.Msg;
public class BSimH2FileDBConnectionManager { public class BSimH2FileDBConnectionManager {
@ -44,7 +45,7 @@ public class BSimH2FileDBConnectionManager {
* Get all H2 File DB data sorces which exist in the JVM. * Get all H2 File DB data sorces which exist in the JVM.
* @return all H2 File DB data sorces * @return all H2 File DB data sorces
*/ */
public static Collection<BSimH2FileDataSource> getAllDataSources() { public static synchronized Collection<BSimH2FileDataSource> getAllDataSources() {
// Create copy to avoid potential concurrent modification // Create copy to avoid potential concurrent modification
return Collections.unmodifiableCollection(new ArrayList<>(dataSourceMap.values())); return Collections.unmodifiableCollection(new ArrayList<>(dataSourceMap.values()));
} }
@ -57,7 +58,7 @@ public class BSimH2FileDBConnectionManager {
* @throws IllegalArgumentException if {@code fileServerInfo} does not specify an * @throws IllegalArgumentException if {@code fileServerInfo} does not specify an
* H2 File DB type. * H2 File DB type.
*/ */
public static BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) { public static synchronized BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) {
if (fileServerInfo.getDBType() != DBType.file) { if (fileServerInfo.getDBType() != DBType.file) {
throw new IllegalArgumentException("expected file info"); 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 * @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. * 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); 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); BSimH2FileDataSource ds = dataSourceMap.get(serverInfo);
if (ds == null) { if (ds == null) {
return; return true;
} }
int n = ds.bds.getNumActive(); int n = ds.bds.getNumActive();
if (n != 0) { if (n != 0 && !force) {
System.out Msg.error(BSimH2FileDBConnectionManager.class,
.println("Unable to remove data source which has " + n + " active connections"); "Unable to remove data source which has " + n + " active connections");
if (!force) { return false;
return;
}
} }
ds.close(); ds.close();
dataSourceMap.remove(serverInfo); dataSourceMap.remove(serverInfo);
BSimVectorStoreManager.remove(serverInfo); BSimVectorStoreManager.remove(serverInfo);
return true;
} }
/** /**
@ -123,20 +124,31 @@ public class BSimH2FileDBConnectionManager {
return serverInfo; return serverInfo;
} }
@Override
public void dispose() { public void dispose() {
BSimH2FileDBConnectionManager.remove(serverInfo, true); BSimH2FileDBConnectionManager.remove(serverInfo, true);
} }
/** /**
* Delete the database files associated with this H2 File DB. When complete * Delete the database files associated with this H2 File DB. This will fail immediately
* this data source will no longer be valid and should no tbe used. * 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() { public synchronized boolean delete() {
dispose();
File dbf = new File(serverInfo.getDBName()); 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(); String name = dbf.getName();
int ix = name.lastIndexOf(BSimServerInfo.H2_FILE_EXTENSION); int ix = name.lastIndexOf(BSimServerInfo.H2_FILE_EXTENSION);
@ -145,6 +157,13 @@ public class BSimH2FileDBConnectionManager {
} }
DeleteDbFiles.execute(dbf.getParent(), name, true); 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(); return bds.getNumActive();
} }
@Override
public int getIdleConnections() {
return bds.getNumIdle();
}
private String getH2FileUrl() { private String getH2FileUrl() {
// Remove H2 db file extension if present // Remove H2 db file extension if present

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

View file

@ -23,8 +23,7 @@ import org.junit.Test;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.gui.BSimSearchPluginTestHelper;
import ghidra.features.bsim.gui.overview.BSimOverviewProvider; import ghidra.features.bsim.gui.overview.BSimOverviewProvider;
import ghidra.features.bsim.gui.overview.BSimOverviewTestHelper; import ghidra.features.bsim.gui.overview.BSimOverviewTestHelper;
import ghidra.features.bsim.gui.search.dialog.*; import ghidra.features.bsim.gui.search.dialog.*;

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.features.bsim.gui; package ghidra.features.bsim.gui;
import ghidra.features.bsim.gui.search.dialog.BSimServerManager;
import ghidra.features.bsim.query.facade.SFQueryServiceFactory; import ghidra.features.bsim.query.facade.SFQueryServiceFactory;
public class BSimSearchPluginTestHelper { public class BSimSearchPluginTestHelper {

View file

@ -65,6 +65,7 @@ public class QueryFilterTest extends AbstractBSimPluginTest {
assertEquals(SQL_TRUTH, sql); assertEquals(SQL_TRUTH, sql);
} }
@Override
protected void initializeTool() throws Exception { protected void initializeTool() throws Exception {
super.initializeTool(); super.initializeTool();
goTo(FUN1_ADDR); goTo(FUN1_ADDR);
@ -80,7 +81,6 @@ public class QueryFilterTest extends AbstractBSimPluginTest {
* *
* @param ids resolution IDs * @param ids resolution IDs
* @return the query string * @return the query string
* @throws SQLException if there is a problem creating the filter
*/ */
private String generateSQL(IDSQLResolution[] ids) { private String generateSQL(IDSQLResolution[] ids) {
try { try {
@ -88,7 +88,8 @@ public class QueryFilterTest extends AbstractBSimPluginTest {
BSimFilter filter = filterSet.getBSimFilter(); BSimFilter filter = filterSet.getBSimFilter();
BSimSqlClause sql = SQLEffects.createFilter(filter, ids, null); BSimSqlClause sql = SQLEffects.createFilter(filter, ids, null);
return sql.whereClause().trim(); return sql.whereClause().trim();
} catch (SQLException e) { }
catch (SQLException e) {
throw new AssertException(e); throw new AssertException(e);
} }
} }

View file

@ -15,8 +15,7 @@
*/ */
package ghidra.features.bsim.gui.overview; package ghidra.features.bsim.gui.overview;
import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.gui.BSimSearchPluginTestHelper;
import ghidra.features.bsim.gui.search.dialog.*; import ghidra.features.bsim.gui.search.dialog.*;
import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.FunctionDatabase; import ghidra.features.bsim.query.FunctionDatabase;

View file

@ -18,7 +18,7 @@ package ghidra.features.bsim.gui.search.dialog;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -30,7 +30,6 @@ import ghidra.features.bsim.query.SQLFunctionDatabase;
import ghidra.features.bsim.query.client.*; import ghidra.features.bsim.query.client.*;
import ghidra.features.bsim.query.facade.FunctionDatabaseTestDouble; import ghidra.features.bsim.query.facade.FunctionDatabaseTestDouble;
import ghidra.features.bsim.query.protocol.BSimFilter; import ghidra.features.bsim.query.protocol.BSimFilter;
import ghidra.program.database.symbol.FunctionSymbol;
/** /**
* Tests the filtering components of BSim accessible from the UI. This will cover the * Tests the filtering components of BSim accessible from the UI. This will cover the
@ -44,9 +43,9 @@ import ghidra.program.database.symbol.FunctionSymbol;
*/ */
public class BSimFilterPanelTest extends AbstractBSimPluginTest { public class BSimFilterPanelTest extends AbstractBSimPluginTest {
private Set<FunctionSymbol> selectedFunctions = new HashSet<>();
private BSimFilterPanel filterPanel; private BSimFilterPanel filterPanel;
@Override
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
@ -57,6 +56,7 @@ public class BSimFilterPanelTest extends AbstractBSimPluginTest {
filterPanel = BSimSearchDialogTestHelper.getFilterPanel(searchDialog); filterPanel = BSimSearchDialogTestHelper.getFilterPanel(searchDialog);
} }
@Override
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
close(searchDialog); close(searchDialog);

View file

@ -17,8 +17,7 @@ package ghidra.features.bsim.gui.search.dialog;
import java.util.Set; import java.util.Set;
import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.gui.BSimSearchPluginTestHelper;
import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.FunctionDatabase; import ghidra.features.bsim.query.FunctionDatabase;
import ghidra.features.bsim.query.facade.TestBSimServerInfo; import ghidra.features.bsim.query.facade.TestBSimServerInfo;

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.query.inmemory; package ghidra.features.bsim.query.file;
import static org.junit.Assert.*; 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.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.Error; import ghidra.features.bsim.query.FunctionDatabase.Error;
import ghidra.features.bsim.query.description.DatabaseInformation; 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.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
import ghidra.features.bsim.query.protocol.CreateDatabase; import ghidra.features.bsim.query.protocol.CreateDatabase;
import ghidra.features.bsim.query.protocol.ResponseInfo; import ghidra.features.bsim.query.protocol.ResponseInfo;
@ -50,7 +49,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
@After @After
public void tearDown() { public void tearDown() {
//cleanup(); cleanup();
} }
private File getTempDbDir() { private File getTempDbDir() {
@ -77,7 +76,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
} }
private BSimServerInfo createDatabase(String databaseName, List<String> tags, private BSimServerInfo createDatabase(String databaseName, List<String> tags,
List<String> execats, String expectedError) { List<String> execats, String expectedError) {
BSimServerInfo h2DbInfo = getBsimServerInfo(databaseName); BSimServerInfo h2DbInfo = getBsimServerInfo(databaseName);
Msg.debug(this, "Creating H2 File DB: " + h2DbInfo); Msg.debug(this, "Creating H2 File DB: " + h2DbInfo);

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.query.test; package ghidra.features.bsim.query.test;
import static org.junit.Assert.*; import static org.junit.Assert.*;

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.query.test; package ghidra.features.bsim.query.test;
import java.io.*; import java.io.*;