GP-4085 Added ability to add VTSession to a shared repository

This commit is contained in:
ghidra1 2023-12-07 13:31:56 -05:00
parent b8f004c792
commit c3386b72a2
33 changed files with 1063 additions and 565 deletions

View file

@ -424,7 +424,6 @@ public class HeadlessAnalyzer {
if (locator.getProjectDir().exists()) { if (locator.getProjectDir().exists()) {
project = openProject(locator); project = openProject(locator);
AppInfo.setActiveProject(project);
} }
else { else {
if (options.runScriptsNoImport) { if (options.runScriptsNoImport) {
@ -441,7 +440,6 @@ public class HeadlessAnalyzer {
Msg.info(this, "Creating " + (options.deleteProject ? "temporary " : "") + Msg.info(this, "Creating " + (options.deleteProject ? "temporary " : "") +
"project: " + locator); "project: " + locator);
project = getProjectManager().createProject(locator, null, false); project = getProjectManager().createProject(locator, null, false);
AppInfo.setActiveProject(project);
} }
try { try {
@ -459,7 +457,6 @@ public class HeadlessAnalyzer {
} }
finally { finally {
project.close(); project.close();
AppInfo.setActiveProject(null);
if (!options.runScriptsNoImport && options.deleteProject) { if (!options.runScriptsNoImport && options.deleteProject) {
FileUtilities.deleteDir(locator.getProjectDir()); FileUtilities.deleteDir(locator.getProjectDir());
locator.getMarkerFile().delete(); locator.getMarkerFile().delete();
@ -1841,11 +1838,13 @@ public class HeadlessAnalyzer {
HeadlessProject(HeadlessGhidraProjectManager projectManager, GhidraURLConnection connection) HeadlessProject(HeadlessGhidraProjectManager projectManager, GhidraURLConnection connection)
throws IOException { throws IOException {
super(projectManager, connection); super(projectManager, connection);
AppInfo.setActiveProject(this);
} }
HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator) HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator)
throws NotOwnerException, LockException, IOException { throws NotOwnerException, LockException, IOException {
super(projectManager, projectLocator, false); super(projectManager, projectLocator, false);
AppInfo.setActiveProject(this);
} }
} }

View file

@ -45,7 +45,7 @@ import ghidra.util.task.TaskMonitor;
public class ProgramOpener { public class ProgramOpener {
private final Object consumer; private final Object consumer;
private String openPromptText = "Open"; private String openPromptText = "Open";
private boolean silent = false; // if true operation does not permit interaction private boolean silent = SystemUtilities.isInHeadlessMode(); // if true operation does not permit interaction
private boolean noCheckout = false; // if true operation should not perform optional checkout private boolean noCheckout = false; // if true operation should not perform optional checkout
/** /**
@ -253,8 +253,9 @@ public class ProgramOpener {
if (domainFile.checkout(dialog.exclusiveCheckout(), monitor)) { if (domainFile.checkout(dialog.exclusiveCheckout(), monitor)) {
return; return;
} }
Msg.showError(this, null, "Checkout Failed", "Exclusive checkout failed for: " + Msg.showError(this, null, "Checkout Failed",
domainFile.getName() + "\nOne or more users have file checked out!"); "Exclusive checkout failed for: " + domainFile.getName() +
"\nOne or more users have file checked out!");
} }
catch (CancelledException e) { catch (CancelledException e) {
// we don't care, the task has been cancelled // we don't care, the task has been cancelled

View file

@ -13,6 +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.
*/ */
// A script that runs Auto Version Tracking given the options set in one of the following ways: // A script that runs Auto Version Tracking given the options set in one of the following ways:
// 1. If script is run from the CodeBrowser, the GUI options are set in a pop up dialog by user. // 1. If script is run from the CodeBrowser, the GUI options are set in a pop up dialog by user.
// 2. If script is run in headless mode either the defaults provided by the script are used or the // 2. If script is run in headless mode either the defaults provided by the script are used or the
@ -129,6 +130,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
Program otherProgram = startupValues.getProgram("Please select the other program", this, Program otherProgram = startupValues.getProgram("Please select the other program", this,
state.getTool(), autoUpgradeIfNeeded); state.getTool(), autoUpgradeIfNeeded);
VTSession session = null;
try {
if (isCurrentProgramSourceProg) { if (isCurrentProgramSourceProg) {
sourceProgram = currentProgram; sourceProgram = currentProgram;
destinationProgram = otherProgram; destinationProgram = otherProgram;
@ -145,8 +148,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
// Need to end the script transaction or it interferes with vt things that need locks // Need to end the script transaction or it interferes with vt things that need locks
end(true); end(true);
VTSession session = session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
if (folder.getFile(name) == null) { if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor); folder.createFile(name, session, monitor);
@ -180,17 +182,21 @@ public class AutoVersionTrackingScript extends GhidraScript {
TaskLauncher.launch(autoVtTask); TaskLauncher.launch(autoVtTask);
// if not running headless user can decide whether to save or not // Save destination program and session changes
// if running headless - must save here or nothing that was done in this script will be
// accessible later.
if (isRunningHeadless()) {
otherProgram.save("Updated with Auto Version Tracking", monitor); otherProgram.save("Updated with Auto Version Tracking", monitor);
session.save(); session.save();
}
println(autoVtTask.getStatusMsg()); println(autoVtTask.getStatusMsg());
}
finally {
if (otherProgram != null) {
otherProgram.release(this); otherProgram.release(this);
} }
if (session != null) {
session.release(this);
}
}
}
/** /**
* Method to determine if there is an existing VTSession in the given folder with the given name * Method to determine if there is an existing VTSession in the given folder with the given name

View file

@ -63,8 +63,7 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
return; return;
} }
VTSession session = VTSession session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
// it seems clunky to have to create this separately, but I'm not sure how else to do it // it seems clunky to have to create this separately, but I'm not sure how else to do it
folder.createFile(name, session, monitor); folder.createFile(name, session, monitor);

View file

@ -20,14 +20,14 @@
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import ghidra.feature.vt.GhidraVersionTrackingScript; import ghidra.feature.vt.AbstractGhidraVersionTrackingScript;
import ghidra.feature.vt.api.main.VTMatch; import ghidra.feature.vt.api.main.VTMatch;
import ghidra.framework.model.Project; import ghidra.framework.model.Project;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Msg; import ghidra.util.Msg;
public class FindChangedFunctionsScript extends GhidraVersionTrackingScript { public class FindChangedFunctionsScript extends AbstractGhidraVersionTrackingScript {
private Program p1; private Program p1;
private Program p2; private Program p2;

View file

@ -20,10 +20,10 @@
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import ghidra.feature.vt.GhidraVersionTrackingScript; import ghidra.feature.vt.AbstractGhidraVersionTrackingScript;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
public class OpenVersionTrackingSessionScript extends GhidraVersionTrackingScript { public class OpenVersionTrackingSessionScript extends AbstractGhidraVersionTrackingScript {
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
@ -33,6 +33,9 @@ public class OpenVersionTrackingSessionScript extends GhidraVersionTrackingScrip
} }
private void acceptMatchesWithGoodConfidence() throws Exception { private void acceptMatchesWithGoodConfidence() throws Exception {
VTSession vtSession = getVTSession();
println("Working on session: " + vtSession); println("Working on session: " + vtSession);
List<VTMatchSet> matchSets = vtSession.getMatchSets(); List<VTMatchSet> matchSets = vtSession.getMatchSets();

View file

@ -25,50 +25,81 @@ import ghidra.feature.vt.api.util.VTOptions;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.util.InvalidNameException;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
public abstract class GhidraVersionTrackingScript extends GhidraScript { public abstract class AbstractGhidraVersionTrackingScript extends GhidraScript {
protected VTSession vtSession; private VTSession vtSession;
protected Program sourceProgram; private Program sourceProgram;
protected Program destinationProgram; private Program destinationProgram;
private int transactionID; private int transactionID;
public void createVersionTrackingSession(String sourceProgramPath, protected VTSession getVTSession() {
String destinationProgramPath) throws Exception { return vtSession;
}
protected Program getSourceProgram() {
return sourceProgram;
}
protected Program getDestinationProgram() {
return destinationProgram;
}
public VTSession createVersionTrackingSession(String sourceProgramPath,
String destinationProgramPath)
throws VersionException, CancelledException, IOException {
if (vtSession != null) { if (vtSession != null) {
throw new RuntimeException("Attempted to open a new session with one already open!"); throw new RuntimeException("Attempted to open a new session with one already open!");
} }
try {
sourceProgram = openProgram(sourceProgramPath); sourceProgram = openProgram(sourceProgramPath);
destinationProgram = openProgram(destinationProgramPath); destinationProgram = openProgram(destinationProgramPath);
createVersionTrackingSession("New Session", sourceProgram, destinationProgram); vtSession = new VTSessionDB("New Session", sourceProgram, destinationProgram, this);
transactionID = vtSession.startTransaction("VT Script");
}
finally {
if (vtSession == null) {
closeVersionTrackingSession();
}
}
return vtSession;
} }
public void createVersionTrackingSession(String name, Program source, Program destination) public VTSession createVersionTrackingSession(String name, Program source, Program destination)
throws Exception { throws IOException {
if (vtSession != null) { if (vtSession != null) {
throw new RuntimeException("Attempted to create a new session with one already open!"); throw new RuntimeException("Attempted to create a new session with one already open!");
} }
try {
sourceProgram = source; sourceProgram = source;
destinationProgram = destination;
if (!sourceProgram.isUsedBy(this)) {
sourceProgram.addConsumer(this); sourceProgram.addConsumer(this);
}
if (!destinationProgram.isUsedBy(this)) {
destinationProgram.addConsumer(this);
}
vtSession = VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this); destinationProgram = destination;
destinationProgram.addConsumer(this);
vtSession = new VTSessionDB(name, sourceProgram, destinationProgram, this);
transactionID = vtSession.startTransaction("VT Script"); transactionID = vtSession.startTransaction("VT Script");
} }
finally {
if (vtSession == null) {
closeVersionTrackingSession();
}
}
return vtSession;
}
public void openVersionTrackingSession(String path) throws Exception { public VTSession openVersionTrackingSession(String path)
throws VersionException, CancelledException, IOException {
if (vtSession != null) { if (vtSession != null) {
throw new RuntimeException("Attempted to open a session with one already open!"); throw new RuntimeException("Attempted to open a session with one already open!");
} }
@ -79,51 +110,72 @@ public abstract class GhidraVersionTrackingScript extends GhidraScript {
DomainFile file = state.getProject().getProjectData().getFile(path); DomainFile file = state.getProject().getProjectData().getFile(path);
vtSession = (VTSessionDB) file.getDomainObject(this, true, true, monitor); vtSession = (VTSessionDB) file.getDomainObject(this, true, true, monitor);
sourceProgram = vtSession.getSourceProgram(); sourceProgram = vtSession.getSourceProgram();
destinationProgram = vtSession.getDestinationProgram();
if (!sourceProgram.isUsedBy(this)) {
sourceProgram.addConsumer(this); sourceProgram.addConsumer(this);
} destinationProgram = vtSession.getDestinationProgram();
if (!destinationProgram.isUsedBy(this)) {
destinationProgram.addConsumer(this); destinationProgram.addConsumer(this);
}
transactionID = vtSession.startTransaction("VT Script"); transactionID = vtSession.startTransaction("VT Script");
return vtSession;
} }
public void saveVersionTrackingSession() throws IOException { public void saveVersionTrackingSession() throws IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to save a session when not open!");
}
vtSession.endTransaction(transactionID, true); vtSession.endTransaction(transactionID, true);
try {
vtSession.save(); vtSession.save();
}
finally {
transactionID = vtSession.startTransaction("VT Script"); transactionID = vtSession.startTransaction("VT Script");
} }
}
public void saveSessionAs(String path, String name) throws Exception { public void saveSessionAs(String path, String name)
throws InvalidNameException, CancelledException, IOException {
if (vtSession != null) {
throw new RuntimeException("Attempted to save a session when not open!");
}
vtSession.endTransaction(transactionID, true);
try {
DomainFolder folder = state.getProject().getProjectData().getFolder(path); DomainFolder folder = state.getProject().getProjectData().getFolder(path);
folder.createFile(name, vtSession, monitor); folder.createFile(name, vtSession, monitor);
vtSession.setName(name); vtSession.setName(name);
} }
finally {
transactionID = vtSession.startTransaction("VT Script");
}
}
@Override @Override
public void cleanup(boolean success) { public void cleanup(boolean success) {
closeVersionTrackingSession(); closeVersionTrackingSession();
if (destinationProgram != null) { super.cleanup(success);
closeProgram(destinationProgram);
}
if (sourceProgram != null) {
closeProgram(sourceProgram);
}
sourceProgram = null;
destinationProgram = null;
} }
/**
* This will release the current session and both source and destination programs.
* If either program needs to be held it is the script's responsibility to first retain
* the instance and add itself as a consumer. Any program consumer must release it
* when done using it.
*/
public void closeVersionTrackingSession() { public void closeVersionTrackingSession() {
if (vtSession != null) { if (vtSession != null) {
vtSession.endTransaction(transactionID, true); vtSession.endTransaction(transactionID, true);
vtSession.release(this); vtSession.release(this);
vtSession = null;
}
if (destinationProgram != null) {
destinationProgram.release(this);
destinationProgram = null;
}
if (sourceProgram != null) {
sourceProgram.release(this);
sourceProgram = null;
}
} }
} private Program openProgram(String path)
public Program openProgram(String path)
throws VersionException, CancelledException, IOException { throws VersionException, CancelledException, IOException {
if (state.getProject() == null) { if (state.getProject() == null) {
throw new RuntimeException("No project open."); throw new RuntimeException("No project open.");
@ -132,11 +184,6 @@ public abstract class GhidraVersionTrackingScript extends GhidraScript {
return (Program) file.getDomainObject(this, true, true, monitor); return (Program) file.getDomainObject(this, true, true, monitor);
} }
@Override
public void closeProgram(Program program) {
program.release(this);
}
public Set<String> getSourceFunctions() { public Set<String> getSourceFunctions() {
if (vtSession == null) { if (vtSession == null) {
throw new RuntimeException("You must have an open vt session"); throw new RuntimeException("You must have an open vt session");

View file

@ -13,33 +13,30 @@
* 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.feature.vt.api.impl; package ghidra.feature.vt.api.db;
import java.io.IOException; import java.io.IOException;
import javax.swing.Icon; import javax.swing.Icon;
import db.DBHandle; import db.DBHandle;
import db.OpenMode;
import db.buffers.BufferFile; import db.buffers.BufferFile;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.framework.data.DBContentHandler; import ghidra.framework.data.DBContentHandler;
import ghidra.framework.data.DomainObjectMergeManager; import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.ChangeSet; import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.util.InvalidNameException; import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> { public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
private static Icon ICON = new GIcon("icon.version.tracking.session.content.type"); public static final String CONTENT_TYPE = "VersionTracking";
public final static String CONTENT_TYPE = "VersionTracking"; private static final Icon ICON = new GIcon("icon.version.tracking.session.content.type");
@Override @Override
public long createFile(FileSystem fs, FileSystem userfs, String path, String name, public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
@ -74,22 +71,40 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
return "Version Tracking"; return "Version Tracking";
} }
private void checkContentAndExclusiveCheckout(FolderItem item) throws IOException {
String contentType = item.getContentType();
if (!contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType);
}
// NOTE: item.isVersioned indicates that item is located on versioned filesystem
// and is not checked-out, otheriwse assume item in local filesystem and must
// ensure if any checkout is exclusive.
if (item.isVersioned() || (item.isCheckedOut() && !item.isCheckedOutExclusive())) {
throw new IOException(
"Unsupported VT Session use: session file must be checked-out exclusive");
}
}
@Override @Override
public VTSessionDB getDomainObject(FolderItem item, FileSystem userfs, long checkoutId, public VTSessionDB getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor) boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException { throws IOException, CancelledException, VersionException {
String contentType = item.getContentType(); checkContentAndExclusiveCheckout(item);
if (!contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType); if (item.isReadOnly()) {
throw new ReadOnlyException("VT Session file is set read-only which prevents its use");
} }
try { try {
DatabaseItem dbItem = (DatabaseItem) item; DatabaseItem dbItem = (DatabaseItem) item;
BufferFile bf = dbItem.openForUpdate(checkoutId); BufferFile bf = dbItem.openForUpdate(checkoutId);
DBHandle dbh = new DBHandle(bf, okToRecover, monitor); DBHandle dbh = new DBHandle(bf, okToRecover, monitor);
boolean success = false; boolean success = false;
try { try {
VTSessionDB db = VTSessionDB.getVTSession(dbh, OpenMode.UPGRADE, consumer, monitor); // NOTE: Always open with DB upgrade enabled
VTSessionDB db = new VTSessionDB(dbh, monitor, consumer);
success = true; success = true;
return db; return db;
} }
@ -99,13 +114,7 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
} }
} }
} }
catch (VersionException e) { catch (VersionException | IOException | CancelledException e) {
throw e;
}
catch (IOException e) {
throw e;
}
catch (CancelledException e) {
throw e; throw e;
} }
catch (Throwable t) { catch (Throwable t) {
@ -134,12 +143,7 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
int minChangeVersion, TaskMonitor monitor) int minChangeVersion, TaskMonitor monitor)
throws IOException, CancelledException, VersionException { throws IOException, CancelledException, VersionException {
String contentType = item.getContentType();
if (!contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType);
}
return getReadOnlyObject(item, -1, false, consumer, monitor); return getReadOnlyObject(item, -1, false, consumer, monitor);
} }
@Override @Override
@ -154,43 +158,14 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
Object consumer, TaskMonitor monitor) Object consumer, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
String contentType = item.getContentType(); checkContentAndExclusiveCheckout(item);
if (contentType != null && !contentType.equals(CONTENT_TYPE)) {
throw new IOException("Unsupported content type: " + contentType); throw new ReadOnlyException("VT Session does not support read-only use");
}
try {
DatabaseItem dbItem = (DatabaseItem) item;
BufferFile bf = dbItem.open();
DBHandle dbh = new DBHandle(bf);
boolean success = false;
try {
VTSessionDB manager =
VTSessionDB.getVTSession(dbh, OpenMode.READ_ONLY, consumer, monitor);
success = true;
return manager;
}
finally {
if (!success) {
dbh.close();
}
}
}
catch (IOException e) {
throw e;
}
catch (Throwable t) {
Msg.error(this, "Get read-only object failed", t);
String msg = t.getMessage();
if (msg == null) {
msg = t.toString();
}
throw new IOException("Open failed: " + msg, t);
}
} }
@Override @Override
public boolean isPrivateContentType() { public boolean isPrivateContentType() {
return true; return false;
} }
} }

View file

@ -26,6 +26,7 @@ import ghidra.feature.vt.api.correlator.program.ImpliedMatchProgramCorrelator;
import ghidra.feature.vt.api.correlator.program.ManualMatchProgramCorrelator; import ghidra.feature.vt.api.correlator.program.ManualMatchProgramCorrelator;
import ghidra.feature.vt.api.impl.*; import ghidra.feature.vt.api.impl.*;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.framework.data.DomainObjectAdapterDB; import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status; import ghidra.framework.model.TransactionInfo.Status;
@ -36,26 +37,30 @@ import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Msg; import ghidra.util.*;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class VTSessionDB extends DomainObjectAdapterDB implements VTSession { public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE }; private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE };
private final static String[] COL_TYPES = new String[] { "Value" }; private final static String[] COL_TYPES = new String[] { "Value" };
private final static Schema SCHEMA = private final static Schema SCHEMA =
new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES); new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES);
private static final String PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs"; // Source and Destination Program IDs are retained within OptionsDB
private static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID"; static final String PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs";
private static final String DESTINATION_PROGRAM_ID_PROPERTY_KEY = "DestinationProgramID"; static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID";
static final String DESTINATION_PROGRAM_ID_PROPERTY_KEY = "DestinationProgramID";
private static final String UNUSED_DEFAULT_NAME = "Untitled"; private static final String UNUSED_DEFAULT_NAME = "Untitled";
private static final int EVENT_NOTIFICATION_DELAY = 500; private static final int EVENT_NOTIFICATION_DELAY = 500;
private static final int EVENT_BUFFER_SIZE = 100;
private static final long MANUAL_MATCH_SET_ID = 0; private static final long MANUAL_MATCH_SET_ID = 0;
private static final long IMPLIED_MATCH_SET_ID = -1; private static final long IMPLIED_MATCH_SET_ID = -1;
// PropertyTable is used solely to retain DB version
// NOTE: OptionsDB already has a table named "Property Table"
private static final String PROPERTY_TABLE_NAME = "PropertyTable"; private static final String PROPERTY_TABLE_NAME = "PropertyTable";
private static final String DB_VERSION_PROPERTY_NAME = "DB_VERSION"; private static final String DB_VERSION_PROPERTY_NAME = "DB_VERSION";
@ -65,8 +70,11 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
* 14-Nov-2019 - version 2 - Corrected fixed length indexing implementation causing * 14-Nov-2019 - version 2 - Corrected fixed length indexing implementation causing
* change in index table low-level storage for newly * change in index table low-level storage for newly
* created tables. * created tables.
* 16-Feb-2024 - version 3 - No schema change. Version imposed to prevent older versions
* of Ghidra from opening session objects which may have been
* added to version controlled repository.
*/ */
private static final int DB_VERSION = 2; private static final int DB_VERSION = 3;
/** /**
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION any time the * UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION any time the
@ -75,7 +83,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
* if the data's version is >= UPGRADE_REQUIRED_BEFORE_VERSION and <= DB_VERSION. * if the data's version is >= UPGRADE_REQUIRED_BEFORE_VERSION and <= DB_VERSION.
*/ */
// NOTE: Schema upgrades are not currently supported // NOTE: Schema upgrades are not currently supported
private static final int UPGRADE_REQUIRED_BEFORE_VERSION = 1; private static final int UPGRADE_REQUIRED_BEFORE_VERSION = 3;
private VTMatchSetTableDBAdapter matchSetTableAdapter; private VTMatchSetTableDBAdapter matchSetTableAdapter;
private AssociationDatabaseManager associationManager; private AssociationDatabaseManager associationManager;
@ -89,40 +97,119 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
private VTMatchSet impliedMatchSet; private VTMatchSet impliedMatchSet;
private boolean changeSetsModified = false; private boolean changeSetsModified = false;
private Table propertyTable; private Table propertyTable; // used to retain DB version only
/**
* Factory method which constructs a new VTSessionDB using specified source and desitination
* programs.
* @param name name to be assigned to the resulting domain object file
* @param sourceProgram session source program within active project
* @param destinationProgram session destination program open for update within active project
* @param consumer object consumer resposible for the proper release of the returned instance.
* @return new {@link VTSessionDB} object
* @throws IOException if an IO error occurs
* @deprecated {@link #VTSessionDB(String, Program, Program, Object)} should be used instead
*/
@Deprecated(since = "11.1", forRemoval = true)
public static VTSessionDB createVTSession(String name, Program sourceProgram, public static VTSessionDB createVTSession(String name, Program sourceProgram,
Program destinationProgram, Object consumer) throws IOException { Program destinationProgram, Object consumer) throws IOException {
return new VTSessionDB(name, sourceProgram, destinationProgram, consumer);
}
VTSessionDB session = new VTSessionDB(new DBHandle(), consumer); /**
* Construct a new VTSessionDB using specified source and desitination programs.
* @param name name to be assigned to the resulting domain object file
* @param sourceProgram session source program within active project
* @param destinationProgram session destination program open for update within active project
* @param consumer object consumer resposible for the proper release of the returned instance.
* @throws IOException if an IO error occurs
*/
public VTSessionDB(String name, Program sourceProgram, Program destinationProgram,
Object consumer) throws IOException {
super(new DBHandle(), UNUSED_DEFAULT_NAME, EVENT_NOTIFICATION_DELAY, consumer);
int ID = session.startTransaction("Constructing New Version Tracking Match Set"); propertyTable = dbh.getTable(PROPERTY_TABLE_NAME);
int ID = startTransaction("Constructing New Version Tracking Match Set");
try { try {
session.propertyTable = session.dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA); propertyTable = dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
session.matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(session.dbh); matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(dbh);
session.associationManager = associationManager = AssociationDatabaseManager.createAssociationManager(dbh, this);
AssociationDatabaseManager.createAssociationManager(session.dbh, session); matchTagAdapter = VTMatchTagDBAdapter.createAdapter(dbh);
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.dbh);
session.initializePrograms(sourceProgram, destinationProgram); initializePrograms(sourceProgram, destinationProgram, true);
session.createMatchSet(
new ManualMatchProgramCorrelator(sourceProgram, destinationProgram), createMatchSet(new ManualMatchProgramCorrelator(sourceProgram, destinationProgram),
MANUAL_MATCH_SET_ID); MANUAL_MATCH_SET_ID);
session.createMatchSet( createMatchSet(new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
IMPLIED_MATCH_SET_ID); IMPLIED_MATCH_SET_ID);
session.updateVersion();
updateVersion();
} }
finally { finally {
session.endTransaction(ID, true); endTransaction(ID, true);
} }
try { try {
session.addSynchronizedDomainObject(destinationProgram); addSynchronizedDomainObject(destinationProgram);
} }
catch (Exception e) { catch (Exception e) {
session.close(); close();
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e);
} }
return session; }
/**
* Construct an existing VT session object and open with UPGRADE enabled.
* The caller (i.e., content handler) must ensure that project has exclusive access to
* the domain file before it was open and {@link DBHandle} supplied.
* @param dbHandle database handle
* @param monitor TaskMonitor that allows the open to be canceled.
* @param consumer the object that keeping the session open.
* @throws IOException if an error accessing the database occurs.
* @throws VersionException if database version does not match implementation, UPGRADE may be possible.
* @throws CancelledException if instantiation is canceled by monitor
*/
@SuppressWarnings("unused")
VTSessionDB(DBHandle dbHandle, TaskMonitor monitor, Object consumer)
throws VersionException, IOException, CancelledException {
super(dbHandle, UNUSED_DEFAULT_NAME, EVENT_NOTIFICATION_DELAY, consumer);
// openMode forced to UPGRADE since we do not support read-only mode
// It is assumed we always have exclusive access to the underlying database
OpenMode openMode = OpenMode.UPGRADE;
propertyTable = dbHandle.getTable(PROPERTY_TABLE_NAME);
int storedVersion = getVersion();
if (storedVersion > DB_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
// The following version logic holds true for DB_VERSION <= 3 which assume no additional
// DB index tables will be added when open for update/upgrade. This may not hold
// true for future revisions associated with table schema changes in which case the
// UPGRADE_REQUIRED_BEFORE_VERSION value should equal DB_VERSION. Current logic
// assumes no schema changes will be made during upgrade.
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) {
if (openMode != OpenMode.UPGRADE) { // should always be open with UPGRADE mode
throw new VersionException(
"Version Tracking Sessions do not support schema upgrades.",
VersionException.OLDER_VERSION, true);
}
withTransaction("Update DBVersion", () -> updateVersion());
clearUndo(false);
changed = true;
}
// NOTE: code below will not make changes (no transaction is open)
// Additional supported required to facilitate schema change during upgrade if needed.
matchSetTableAdapter = VTMatchSetTableDBAdapter.getAdapter(dbHandle, openMode, monitor);
associationManager =
AssociationDatabaseManager.getAssociationManager(dbHandle, this, openMode, monitor);
matchTagAdapter = VTMatchTagDBAdapter.getAdapter(dbHandle, openMode, monitor);
loadMatchSets(openMode, monitor);
} }
private void updateVersion() throws IOException { private void updateVersion() throws IOException {
@ -131,47 +218,174 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
propertyTable.putRecord(record); propertyTable.putRecord(record);
} }
public static VTSessionDB getVTSession(DBHandle dbHandle, OpenMode openMode, Object consumer, private int getVersion() throws IOException {
TaskMonitor monitor) throws VersionException, IOException { // DB Version was added in release (11/6/2012)
// if record does not exist return 0;
VTSessionDB session = new VTSessionDB(dbHandle, consumer); if (propertyTable == null) {
int storedVersion = session.getVersion(); return 0;
if (storedVersion > DB_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
} }
// The following version logic holds true for DB_VERSION=2 which assumes no additional DBRecord record = propertyTable.getRecord(new StringField(DB_VERSION_PROPERTY_NAME));
// DB index tables will be added when open for update/upgrade. This will not hold
// true for future revisions associated with table schema changes in which case the if (record != null) {
// UPGRADE_REQUIRED_BEFORE_VERSION value should equal DB_VERSION. String s = record.getString(0);
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) { try {
throw new VersionException("Version Tracking Sessions do not support schema upgrades."); return Integer.parseInt(s);
}
catch (NumberFormatException e) {
// just use default
}
}
return 0;
} }
session.matchSetTableAdapter = @Override
VTMatchSetTableDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor); protected void setDomainFile(DomainFile df) throws DomainObjectException {
session.associationManager = DomainFolder parent = df.getParent();
AssociationDatabaseManager.getAssociationManager(dbHandle, session, openMode, monitor); if (parent != null && sourceProgram == null) {
session.matchTagAdapter = try {
VTMatchTagDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor); openSourceAndDestinationPrograms(parent.getProjectData());
session.loadMatchSets(openMode, monitor); }
return session; catch (IOException e) {
throw new DomainObjectException(e);
}
}
super.setDomainFile(df);
}
/**
* Open associated source and destination program files and complete session initialization.
* @param projectData active project data
* @throws IOException if source or destination program not found within specified project
* or an error occured while opening them (e.g., upgrade required).
*/
private void openSourceAndDestinationPrograms(ProjectData projectData) throws IOException {
String sourceProgramID = getSourceProgramID();
String destinationProgramID = getDestinationProgramID();
DomainFile sourceFile = projectData.getFileByID(sourceProgramID);
DomainFile destinationFile = projectData.getFileByID(destinationProgramID);
if (sourceFile == null) {
throw new IOException("Source program is missing for this Version Tracking Session!");
}
if (destinationFile == null) {
throw new IOException(
"Destination program is missing for this Version Tracking Session!");
}
// Must ensure that destination program file can be updated
VTSessionFileUtil.validateDestinationProgramFile(destinationFile, true,
SystemUtilities.isInHeadlessMode());
VTSessionFileUtil.validateSourceProgramFile(sourceFile, true);
sourceProgram = openProgram(sourceFile, true);
if (sourceProgram != null) {
destinationProgram = openProgram(destinationFile, false);
}
if (sourceProgram == null || destinationProgram == null) {
StringBuilder buffer = new StringBuilder(
"Session not opened because one or both programs did not open.\n");
if (sourceProgram != null) {
sourceProgram.release(this);
sourceProgram = null;
}
else {
buffer.append("\tUnable to open source program \"" + sourceFile + "\"\n");
}
if (destinationProgram != null) {
destinationProgram.release(this);
destinationProgram = null;
}
else {
buffer.append("\tUnable to open destination program \"" + destinationFile + "\"\n");
}
throw new IOException(buffer.toString());
}
associationManager.sessionInitialized();
try {
addSynchronizedDomainObject(destinationProgram);
}
catch (Exception e) {
sourceProgram.release(this);
sourceProgram = null;
destinationProgram.release(this);
destinationProgram = null;
throw new IOException(e.getMessage());
}
}
private Program openProgram(DomainFile domainFile, boolean isSource) {
String type = isSource ? "VT Source Program" : "VT Destination Program";
if (SystemUtilities.isInHeadlessMode()) {
try {
return (Program) domainFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e); // unexpected
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), type, "open",
e);
}
catch (IOException e) {
Msg.showError(this, null, "Can't open " + type + ": " + domainFile.getName(),
e.getMessage());
}
return null;
}
// Headed GUI Mode
OpenProgramTask openTask = new OpenProgramTask(domainFile, this);
openTask.setOpenPromptText("Open " + type);
TaskLauncher.launch(openTask);
OpenProgramRequest openProgram = openTask.getOpenProgram();
return openProgram != null ? openProgram.getProgram() : null;
}
public String getSourceProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(SOURCE_PROGRAM_ID_PROPERTY_KEY, "");
}
public String getDestinationProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(DESTINATION_PROGRAM_ID_PROPERTY_KEY, "");
} }
@SuppressWarnings("hiding") @SuppressWarnings("hiding")
// this is from our constructor // this is from our constructor
private void initializePrograms(Program sourceProgram, Program destinationProgram) { private void initializePrograms(Program sourceProgram, Program destinationProgram,
boolean rememberProgramIds) throws IOException {
if (!destinationProgram.canSave()) {
throw new ReadOnlyException(
"VT Session destination program is read-only which prevents its use");
}
this.sourceProgram = sourceProgram; this.sourceProgram = sourceProgram;
this.destinationProgram = destinationProgram;
sourceProgram.addConsumer(this); sourceProgram.addConsumer(this);
this.destinationProgram = destinationProgram;
destinationProgram.addConsumer(this); destinationProgram.addConsumer(this);
if (rememberProgramIds) {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME); Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
DomainFile sourceDomainFile = sourceProgram.getDomainFile(); DomainFile sourceDomainFile = sourceProgram.getDomainFile();
properties.setString(SOURCE_PROGRAM_ID_PROPERTY_KEY, sourceDomainFile.getFileID()); properties.setString(SOURCE_PROGRAM_ID_PROPERTY_KEY, sourceDomainFile.getFileID());
DomainFile destinationDomainFile = destinationProgram.getDomainFile(); DomainFile destinationDomainFile = destinationProgram.getDomainFile();
properties.setString(DESTINATION_PROGRAM_ID_PROPERTY_KEY, properties.setString(DESTINATION_PROGRAM_ID_PROPERTY_KEY,
destinationDomainFile.getFileID()); destinationDomainFile.getFileID());
}
} }
@Override @Override
@ -202,119 +416,6 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
sourceProgram.addConsumer(this); sourceProgram.addConsumer(this);
} }
public String getSourceProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(SOURCE_PROGRAM_ID_PROPERTY_KEY, "");
}
public String getDestinationProgramID() {
Options properties = getOptions(PROGRAM_ID_PROPERTYLIST_NAME);
return properties.getString(DESTINATION_PROGRAM_ID_PROPERTY_KEY, "");
}
private VTSessionDB(DBHandle dbHandle, Object consumer) {
super(dbHandle, UNUSED_DEFAULT_NAME, EVENT_NOTIFICATION_DELAY, consumer);
propertyTable = dbHandle.getTable(PROPERTY_TABLE_NAME);
}
public int getVersion() throws IOException {
// DB Version was added in release (11/6/2012)
// if record does not exist return 0;
if (propertyTable == null) {
return 0;
}
DBRecord record = propertyTable.getRecord(new StringField(DB_VERSION_PROPERTY_NAME));
if (record != null) {
String s = record.getString(0);
try {
return Integer.parseInt(s);
}
catch (NumberFormatException e) {
// just use default
}
}
return 0;
}
@Override
protected void setDomainFile(DomainFile df) {
super.setDomainFile(df);
DomainFolder parent = df.getParent();
if (parent == null) {
return;
}
if (sourceProgram != null) { // source and destination are already open
return;
}
ProjectData projectData = parent.getProjectData();
String sourceProgramID = getSourceProgramID();
String destinationProgramID = getDestinationProgramID();
DomainFile sourceFile = projectData.getFileByID(sourceProgramID);
DomainFile destinationFile = projectData.getFileByID(destinationProgramID);
if (sourceFile == null) {
throw new RuntimeException(
"Source program is missing for this Version Tracking Session!");
}
if (destinationFile == null) {
throw new RuntimeException(
"Destination program is missing for this Version Tracking Session!");
}
sourceProgram = openProgram(sourceFile, true);
if (sourceProgram != null) {
destinationProgram = openProgram(destinationFile, false);
}
if (sourceProgram == null || destinationProgram == null) {
StringBuilder buffer = new StringBuilder(
"Session not opened because one or both programs did not open.\n");
if (sourceProgram != null) {
sourceProgram.release(this);
sourceProgram = null;
}
else {
buffer.append("\tUnable to open source program \"" + sourceFile + "\"\n");
}
if (destinationProgram != null) {
destinationProgram.release(this);
destinationProgram = null;
}
else {
buffer.append("\tUnable to open destination program \"" + destinationFile + "\"\n");
}
throw new RuntimeException(buffer.toString());
}
associationManager.sessionInitialized();
try {
addSynchronizedDomainObject(destinationProgram);
}
catch (Exception e) {
sourceProgram.release(this);
destinationProgram.release(this);
throw new RuntimeException(e.getMessage());
}
}
private Program openProgram(DomainFile domainFile, boolean isSource) {
OpenProgramTask openTask = new OpenProgramTask(domainFile, this);
String type = isSource ? "(source program)" : "(destination program)";
openTask.setOpenPromptText("Open " + type);
TaskLauncher.launch(openTask);
OpenProgramRequest openProgram = openTask.getOpenProgram();
return openProgram != null ? openProgram.getProgram() : null;
}
@Override @Override
public void release(Object consumer) { public void release(Object consumer) {
super.release(consumer); super.release(consumer);

View file

@ -0,0 +1,186 @@
/* ###
* 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.feature.vt.api.util;
import java.io.IOException;
import ghidra.app.util.dialog.CheckoutDialog;
import ghidra.app.util.task.ProgramOpener;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.remote.User;
import ghidra.program.database.ProgramDB;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
/**
* {@link VTSessionFileUtil} provides methods for checking {@link VTSessionDB} source and
* destination program files prior to being opened and used during session instantiation.
*/
public class VTSessionFileUtil {
// static utility class
private VTSessionFileUtil() {
}
/**
* Validate a VT source program to ensure it meets minimum criteria to open with a VTSession.
* The following validation checks are performed:
* <ul>
* <li>file must correspond to a ProgramDB</li>
* </ul>
* If an error is thrown it is intended to be augmented for proper presentation.
*
* @param file VT Session source program domain file
* @param includeFilePathInError if true file path will be appended to any exception throw
* @throws IllegalArgumentException if any VT source program file criteria is not satisfied
*/
public static void validateSourceProgramFile(DomainFile file, boolean includeFilePathInError)
throws IllegalArgumentException {
String error = null;
if (!ProgramDB.class.isAssignableFrom(file.getDomainObjectClass())) {
error = "Source file does not correspond to a Program";
}
if (error != null) {
if (includeFilePathInError) {
error += ":\n" + file.getPathname();
}
throw new IllegalArgumentException(error);
}
}
/**
* Validate a VT destination program to ensure it meets minimum criteria to open with a VTSession.
* GUI mode only: If file is versioned and not checked-out the user may be prompted to perform
* an optional checkout of the file. Prompting for checkout will not occur if this method
* is invoked from the Swing thread or operating in a headless mode.
* The following validation checks are performed:
* <ul>
* <li>file must correspond to a ProgramDB</li>
* <li>file must be contained within the active project</li>
* <li>file must not be marked read-only</li>
* <li>if file is versioned it must be checked-out (user may be prompted to do this)</li>
* </ul>
* If an error is thrown it is intended to be augmented for proper presentation.
*
* @param file VT Session destination program domain file
* @param includeFilePathInError if true file path will be appended to any exception throw
* @param silent if user interaction should not be performed. This should be true if
* filesystem lock is currently held.
* @throws IllegalArgumentException if any VT destination program file criteria is not satisfied
*/
public static void validateDestinationProgramFile(DomainFile file,
boolean includeFilePathInError, boolean silent) throws IllegalArgumentException {
String error = null;
if (!ProgramDB.class.isAssignableFrom(file.getDomainObjectClass())) {
error = "Destination file does not correspond to a Program";
}
else {
DomainFolder folder = file.getParent();
if (folder == null || !folder.isInWritableProject()) {
error = "Destination file must be from active project";
}
else if (file.isReadOnly()) {
error = "Destination file must not be read-only";
}
else if (file.isVersioned()) {
if (!silent) {
doOptionalDestinationProgramCheckout(file);
}
if (!file.isCheckedOut()) {
error = "Versioned destination file must be checked-out for update";
}
}
}
if (error != null) {
if (includeFilePathInError) {
error += ":\n" + file.getPathname();
}
throw new IllegalArgumentException(error);
}
}
/**
* Determine if the specified {@link DomainFile} will permit update.
* @param file domain file
* @return true if file permits update else false
*/
public static boolean canUpdate(DomainFile file) {
DomainFolder folder = file.getParent();
if (folder == null || !folder.isInWritableProject()) {
return false;
}
if (file.isReadOnly()) {
return false;
}
if (file.isVersioned()) {
return false;
}
return true;
}
private static void doOptionalDestinationProgramCheckout(DomainFile file) {
if (SystemUtilities.isInHeadlessMode() || !file.canCheckout()) {
return;
}
User user = file.getParent().getProjectData().getUser();
CheckoutDialog dialog = new CheckoutDialog(file, user);
dialog.setTitle("VT Destination Program not Checked Out");
if (dialog.showDialog() == CheckoutDialog.CHECKOUT) { // uses Swing thread
CheckoutDestinationProgramTask task =
new CheckoutDestinationProgramTask(file, dialog.exclusiveCheckout());
TaskLauncher.launch(task);
}
}
private static class CheckoutDestinationProgramTask extends Task {
private DomainFile file;
boolean exclusiveCheckout;
CheckoutDestinationProgramTask(DomainFile file, boolean exclusiveCheckout) {
super("Checking Out " + file, true, true, true, true);
this.file = file;
this.exclusiveCheckout = exclusiveCheckout;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Checking Out " + file);
try {
if (!file.checkout(exclusiveCheckout, monitor)) {
Msg.showError(ProgramOpener.class, null, "Checkout Failed",
"Exclusive checkout failed for: " + file +
"\nOne or more users have file checked out!");
}
}
catch (IOException e) {
Msg.showError(ProgramOpener.class, null, "Checkout Failed",
"Checkout failed for: " + file + "\n" + e.getMessage());
}
catch (CancelledException e) {
// ignore
}
}
}
}

View file

@ -46,7 +46,7 @@ public interface VTController extends VTSessionSupplier {
@Override @Override
public VTSession getSession(); public VTSession getSession();
public void openVersionTrackingSession(DomainFile domainFile); public boolean openVersionTrackingSession(DomainFile domainFile);
public void openVersionTrackingSession(VTSession session); public void openVersionTrackingSession(VTSession session);

View file

@ -22,31 +22,36 @@ import java.util.*;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import docking.ActionContext; import docking.ActionContext;
import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext;
import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.feature.vt.api.db.VTAssociationDB; import ghidra.feature.vt.api.db.VTAssociationDB;
import ghidra.feature.vt.api.db.VTSessionDB; import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.feature.vt.gui.duallisting.VTListingContext; import ghidra.feature.vt.gui.duallisting.VTListingContext;
import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemContext; import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemContext;
import ghidra.feature.vt.gui.task.SaveTask; import ghidra.feature.vt.gui.task.SaveTask;
import ghidra.feature.vt.gui.task.VtTask; import ghidra.feature.vt.gui.task.VtTask;
import ghidra.feature.vt.gui.util.MatchInfo; import ghidra.feature.vt.gui.util.MatchInfo;
import ghidra.feature.vt.gui.util.MatchInfoFactory; import ghidra.feature.vt.gui.util.MatchInfoFactory;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.DomainObjectAdapterDB; import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.SaveDataDialog; import ghidra.framework.main.SaveDataDialog;
import ghidra.framework.main.projectdata.actions.CheckoutsDialog;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.*; import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.store.ItemCheckoutStatus;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.AddressCorrelation; import ghidra.program.util.AddressCorrelation;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg; import ghidra.util.*;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakValueHashMap; import ghidra.util.datastruct.WeakValueHashMap;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
@ -94,30 +99,193 @@ public class VTControllerImpl
return session; return session;
} }
@Override private boolean checkSessionFileAccess(DomainFile domainFile) {
public void openVersionTrackingSession(DomainFile domainFile) {
if (!checkForUnSavedChanges()) { DomainFolder folder = domainFile.getParent();
return; if (folder == null || !folder.isInWritableProject()) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file use limited to active project only.");
return false;
} }
if (domainFile.isVersioned()) {
if (domainFile.isCheckedOut()) {
if (!domainFile.isCheckedOutExclusive()) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file is checked-out but does not have exclusive access.\n" +
"You must undo checkout and re-checkout with exclusive access.");
return false;
}
if (domainFile.isReadOnly()) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file is set read-only which prevents its use.");
return false;
}
return true;
}
return checkoutSession(domainFile);
}
else if (domainFile.isReadOnly()) { // non-versioned file
Msg.showError(this, null, "Can't open VT Session: " + domainFile,
"VT Session file is set read-only which prevents its use.");
return false;
}
return true;
}
private boolean checkoutSession(DomainFile domainFile) {
Project activeProject = AppInfo.getActiveProject();
RepositoryAdapter repository = activeProject.getRepository();
if (repository != null) {
try { try {
VTSessionDB newSession = ItemCheckoutStatus[] checkouts = domainFile.getCheckouts();
(VTSessionDB) domainFile.getDomainObject(this, true, true, TaskMonitor.DUMMY); if (checkouts.length != 0) {
doOpenSession(newSession); int rc = OptionDialog.showOptionDialogWithCancelAsDefaultButton(null,
"Checkout VT Session",
"VT Session " + domainFile.getName() + " is NOT CHECKED OUT but " +
"is checked-out by another user.\n" +
"Opening VT Session requires an exclusive check out of this file.\n" +
"Do you want to view the list of active checkouts for this file?",
"View Checkout(s)...");
if (rc != OptionDialog.OPTION_ONE) {
return false;
} }
catch (VersionException e) {
Msg.showError(this, null, "Can't open domainFile " + domainFile.getName(), CheckoutsDialog dialog = new CheckoutsDialog(plugin.getTool(),
e.getMessage()); repository.getUser(), domainFile, checkouts);
plugin.getTool().showDialog(dialog);
return false;
} }
catch (CancelledException e) {
Msg.error(this, "Got unexexped cancelled exception", e);
} }
catch (IOException e) { catch (IOException e) {
Msg.showError(this, null, "Can't open " + domainFile.getName(), e.getMessage()); Msg.showError(this, null, "Checkout VT Session Failed: " + domainFile.getName(),
e.getMessage());
return false;
} }
} }
int rc = OptionDialog.showOptionDialogWithCancelAsDefaultButton(null, "Checkout VT Session",
"VT Session " + domainFile.getName() + " is NOT CHECKED OUT.\n" +
"Opening VT Session requires an exclusive check out of this file.\n" +
"Do you want to Check Out this file?",
"Checkout...");
if (rc != OptionDialog.OPTION_ONE) {
return false;
}
TaskLauncher.launchModal("Checkout VT Session", new MonitoredRunnable() {
@Override
public void monitoredRun(TaskMonitor monitor) {
try {
domainFile.checkout(true, monitor);
}
catch (CancelledException e) {
// ignore
}
catch (IOException e) {
Msg.showError(this, null, "Checkout VT Session Failed: " + domainFile.getName(),
e.getMessage());
}
}
});
return domainFile.isCheckedOutExclusive();
}
@Override
public boolean openVersionTrackingSession(DomainFile domainFile) {
if (!VTSession.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
throw new IllegalArgumentException("File does not correspond to a VTSession");
}
if (!checkForUnSavedChanges()) {
return false;
}
try {
if (!checkSessionFileAccess(domainFile)) {
return false;
}
VTSessionDB vtSessionDB = getVTSessionDB(domainFile, this);
if (vtSessionDB != null) {
try {
openVersionTrackingSession(vtSessionDB);
return true;
}
finally {
vtSessionDB.release(this);
}
}
}
catch (CancelledException e) {
// ignore - return false
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), "VT Session",
"open", e);
}
catch (IOException e) {
Msg.showError(this, null, "Can't open VT Session: " + domainFile.getName(),
e.getMessage());
}
return false;
}
private static class OpenVTSessionTask extends Task {
private final Object consumer;
private final DomainFile vtSessionFile;
Exception exception;
VTSessionDB vtSessionDB;
OpenVTSessionTask(DomainFile vtSessionFile, Object consumer) {
super("Opening VT Session", true, false, true, true);
this.vtSessionFile = vtSessionFile;
this.consumer = consumer;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
vtSessionDB =
(VTSessionDB) vtSessionFile.getDomainObject(consumer, true, true, monitor);
}
catch (Exception e) {
exception = e;
}
}
}
private VTSessionDB getVTSessionDB(DomainFile vtSessionFile, Object consumer)
throws IOException, VersionException, CancelledException {
OpenVTSessionTask task = new OpenVTSessionTask(vtSessionFile, consumer);
TaskLauncher.launch(task);
if (task.exception != null) {
if (task.exception instanceof CancelledException ce) {
throw ce;
}
if (task.exception instanceof VersionException ve) {
throw ve;
}
if (task.exception instanceof IOException ioe) {
throw ioe;
}
throw new IOException("VTSessionDB failure", task.exception);
}
return task.vtSessionDB;
}
@Override @Override
public void openVersionTrackingSession(VTSession newSession) { public void openVersionTrackingSession(VTSession newSession) {
// FIXME: new session wizard should have handled existing session before starting -
// should be no need for this check
if (!checkForUnSavedChanges()) { if (!checkForUnSavedChanges()) {
return; return;
} }
@ -595,44 +763,80 @@ public class VTControllerImpl
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class MyFolderListener extends DomainFolderListenerAdapter { private void updateProgram(DomainFile file, boolean isSource) {
@Override String type = isSource ? "Source" : "Destination";
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
/**
* Special handling for when a file is checked-in. The existing program has be moved
* to a proxy file (no longer in the project) so that it can be closed and the program
* re-opened with the new version after the check-in merge.
*/
if (session == null) {
return;
}
if (session.getSourceProgram() != oldObject &&
session.getDestinationProgram() != oldObject) {
return;
}
Program newProgram; Program newProgram;
try { try {
newProgram = (Program) file.getDomainObject(this, false, false, TaskMonitor.DUMMY); newProgram = (Program) file.getDomainObject(this, false, false, TaskMonitor.DUMMY);
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(this, getParentComponent(), "Error opening program " + file, e); Msg.showError(this, getParentComponent(),
"Error opening VT " + type + " Program: " + file, e);
return; return;
} }
if (oldObject == session.getSourceProgram()) { if (isSource) {
session.updateSourceProgram(newProgram); session.updateSourceProgram(newProgram);
} }
else if (oldObject == session.getDestinationProgram()) { else {
session.updateDestinationProgram(newProgram); session.updateDestinationProgram(newProgram);
} }
// List<DomainObjectChangeRecord> events = new ArrayList<DomainObjectChangeRecord>(); // List<DomainObjectChangeRecord> events = new ArrayList<DomainObjectChangeRecord>();
// events.add(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED)); // events.add(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
// domainObjectChanged(new DomainObjectChangedEvent(newProgram, events)); // domainObjectChanged(new DomainObjectChangedEvent(newProgram, events));
matchInfoFactory.clearCache(); matchInfoFactory.clearCache();
fireSessionChanged(); fireSessionChanged();
} }
private class MyFolderListener extends DomainFolderListenerAdapter {
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
if (session == null) {
return;
}
if (session.getSourceProgram() == oldObject) {
updateProgram(file, true);
return;
}
String type;
if (session == oldObject) {
type = "VT Session";
}
else if (session.getDestinationProgram() == oldObject) {
if (VTSessionFileUtil.canUpdate(file)) {
updateProgram(file, false);
return;
}
type = "Destination Program";
}
else {
return;
}
// Session or destination program can no longer be saved to project so we
// have no choice but to close session.
// Since we are already in the Swing thread we need to delay closing so we do
// not continue to block the Swing thread and the checkin which is in progress.
// This allows the DomainFile checkin to complete its processing first.
SwingUtilities.invokeLater(() -> {
Msg.showInfo(this, plugin.getTool().getToolFrame(), "Closing VT Session",
type + " checkin has forced session close.\n" +
"You will be prompted to save any other changes if needed, after which\n" +
"you may reopen the VT Session.");
closeVersionTrackingSession();
// NOTE: a future convenience could be added to attempt reopening of session
});
}
} }
private class OpenSessionTask extends Task { private class OpenSessionTask extends Task {

View file

@ -216,10 +216,10 @@ public class VTPlugin extends Plugin {
for (DomainFile domainFile : data) { for (DomainFile domainFile : data) {
if (domainFile != null && if (domainFile != null &&
VTSession.class.isAssignableFrom(domainFile.getDomainObjectClass())) { VTSession.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
openVersionTrackingSession(domainFile); return controller.openVersionTrackingSession(domainFile);
return true;
} }
} }
DomainFile programFile1 = null; DomainFile programFile1 = null;
DomainFile programFile2 = null; DomainFile programFile2 = null;
for (DomainFile domainFile : data) { for (DomainFile domainFile : data) {
@ -249,10 +249,6 @@ public class VTPlugin extends Plugin {
return false; return false;
} }
private void openVersionTrackingSession(DomainFile domainFile) {
controller.openVersionTrackingSession(domainFile);
}
@Override @Override
public void readConfigState(SaveState saveState) { public void readConfigState(SaveState saveState) {
controller.readConfigState(saveState); controller.readConfigState(saveState);
@ -274,20 +270,18 @@ public class VTPlugin extends Plugin {
@Override @Override
public void readDataState(SaveState saveState) { public void readDataState(SaveState saveState) {
String pathname = saveState.getString("PATHNAME", null); String pathname = saveState.getString("PATHNAME", null);
String location = saveState.getString("PROJECT_LOCATION", null); if (pathname == null) {
String projectName = saveState.getString("PROJECT_NAME", null);
if (location == null || projectName == null) {
return; return;
} }
ProjectLocator url = new ProjectLocator(location, projectName); Project project = tool.getProject();
if (project == null) {
ProjectData projectData = tool.getProject().getProjectData(url);
if (projectData == null) {
Msg.showError(this, tool.getToolFrame(), "File Not Found", "Could not find " + url);
return; return;
} }
ProjectData projectData = project.getProjectData();
DomainFile domainFile = projectData.getFile(pathname); DomainFile domainFile = projectData.getFile(pathname);
if (domainFile == null) {
return;
}
controller.openVersionTrackingSession(domainFile); controller.openVersionTrackingSession(domainFile);
} }
@ -298,21 +292,7 @@ public class VTPlugin extends Plugin {
return; return;
} }
DomainFile domainFile = session.getDomainFile(); DomainFile domainFile = session.getDomainFile();
saveState.putString("PATHNAME", domainFile.getPathname());
String projectLocation = null;
String projectName = null;
String path = null;
ProjectLocator url = domainFile.getProjectLocator();
if (url != null) {
projectLocation = url.getLocation();
projectName = url.getName();
path = domainFile.getPathname();
}
saveState.putString("PROJECT_LOCATION", projectLocation);
saveState.putString("PROJECT_NAME", projectName);
saveState.putString("PATHNAME", path);
} }
@Override @Override

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,10 +15,11 @@
*/ */
package ghidra.feature.vt.gui.wizard; package ghidra.feature.vt.gui.wizard;
import java.io.IOException;
import docking.wizard.WizardState;
import ghidra.feature.vt.api.db.VTSessionDB; import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.gui.plugin.VTController; import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
@ -28,11 +28,6 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import java.awt.EventQueue;
import java.io.IOException;
import docking.wizard.WizardState;
public class CreateNewSessionTask extends Task { public class CreateNewSessionTask extends Task {
private final WizardState<VTWizardStateKey> state; private final WizardState<VTWizardStateKey> state;
private final VTController controller; private final VTController controller;
@ -45,57 +40,41 @@ public class CreateNewSessionTask extends Task {
@Override @Override
public void run(TaskMonitor monitor) { public void run(TaskMonitor monitor) {
VTSession session = null; VTSessionDB session = null;
String name = null; String name = null;
try { try {
Program sourceProgram = (Program) state.get(VTWizardStateKey.SOURCE_PROGRAM); Program sourceProgram = (Program) state.get(VTWizardStateKey.SOURCE_PROGRAM);
Program destinationProgram = (Program) state.get(VTWizardStateKey.DESTINATION_PROGRAM); Program destinationProgram = (Program) state.get(VTWizardStateKey.DESTINATION_PROGRAM);
session = session = new VTSessionDB("New Session", sourceProgram, destinationProgram, this);
VTSessionDB.createVTSession("New Session", sourceProgram, destinationProgram, this);
DomainObjectAdapterDB dobj = null;
if (session instanceof DomainObjectAdapterDB) {
dobj = (DomainObjectAdapterDB) session;
}
sourceProgram.release(controller.getTool()); sourceProgram.release(controller.getTool());
destinationProgram.release(controller.getTool()); destinationProgram.release(controller.getTool());
if (dobj != null) {
name = (String) state.get(VTWizardStateKey.SESSION_NAME); name = (String) state.get(VTWizardStateKey.SESSION_NAME);
DomainFolder folder = (DomainFolder) state.get(VTWizardStateKey.NEW_SESSION_FOLDER); DomainFolder folder = (DomainFolder) state.get(VTWizardStateKey.NEW_SESSION_FOLDER);
try { try {
folder.createFile(name, dobj, monitor); folder.createFile(name, session, monitor);
} }
catch (InvalidNameException e) { catch (InvalidNameException e) {
Msg.showError(this, null, "Invalid Domain Object Name", Msg.showError(this, null, "Invalid Domain Object Name",
"Please report this error; the name should have been checked already"); "Please report this error; the name should have been checked already");
} }
}
final VTSession finalSession = session; controller.openVersionTrackingSession(session);
EventQueue.invokeLater(new Runnable() {
public void run() {
controller.openVersionTrackingSession(finalSession);
releaseDomainObject(finalSession);
}
});
} }
catch (CancelledException e) { catch (CancelledException e) {
// the user cancelled; just cleanup // ignore
releaseDomainObject(session);
} }
catch (IOException e) { catch (IOException e) {
releaseDomainObject(session); Msg.showError(this, null, "Failed to Create Session",
Msg.showError(this, null, "Failed to Create Session", "Failed to create db file: " + "Failed to create db file: " + name, e);
name, e); }
finally {
if (session != null) {
session.release(this);
}
} }
} }
private void releaseDomainObject(VTSession session) {
if (session == null) {
return;
}
((VTSessionDB) session).release(this);
}
} }

View file

@ -15,22 +15,10 @@
*/ */
package ghidra.feature.vt.gui.wizard; package ghidra.feature.vt.gui.wizard;
import java.awt.BorderLayout; import java.awt.*;
import java.awt.GridBagConstraints; import java.util.*;
import java.awt.GridBagLayout;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.BorderFactory; import javax.swing.*;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
@ -38,22 +26,19 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.button.BrowseButton; import docking.widgets.button.BrowseButton;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.wizard.AbstractMageJPanel; import docking.wizard.*;
import docking.wizard.WizardPanelDisplayability;
import docking.wizard.WizardState;
import generic.theme.GIcon; import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Ids.Fonts; import generic.theme.GThemeDefaults.Ids.Fonts;
import generic.theme.Gui; import generic.theme.Gui;
import ghidra.app.util.task.OpenProgramRequest; import ghidra.app.util.task.OpenProgramRequest;
import ghidra.app.util.task.OpenProgramTask; import ghidra.app.util.task.OpenProgramTask;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
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.*;
import ghidra.util.InvalidNameException;
import ghidra.util.StringUtilities;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskLauncher;
/** /**
@ -309,28 +294,31 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
private String createVTSessionName(String sourceName, String destinationName) { private String createVTSessionName(String sourceName, String destinationName) {
// if together they are within the bounds just return session name with both full names // if together they are within the bounds just return session name with both full names
if (sourceName.length() + destinationName.length() <= 2 * VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) { if (sourceName.length() + destinationName.length() <= 2 *
VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
return "VT_" + sourceName + "_" + destinationName; return "VT_" + sourceName + "_" + destinationName;
} }
// give destination name all space not used by source name // give destination name all space not used by source name
if (sourceName.length() < VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) { if (sourceName.length() < VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
int leftover = VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH - sourceName.length(); int leftover = VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH - sourceName.length();
destinationName = destinationName = StringUtilities.trimMiddle(destinationName,
StringUtilities.trimMiddle(destinationName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover); VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover);
return "VT_" + sourceName + "_" + destinationName; return "VT_" + sourceName + "_" + destinationName;
} }
// give source name all space not used by destination name // give source name all space not used by destination name
if (destinationName.length() < VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) { if (destinationName.length() < VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH) {
int leftover = VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH - destinationName.length(); int leftover = VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH - destinationName.length();
sourceName = StringUtilities.trimMiddle(sourceName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover); sourceName = StringUtilities.trimMiddle(sourceName,
VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH + leftover);
return "VT_" + sourceName + "_" + destinationName; return "VT_" + sourceName + "_" + destinationName;
} }
// if both too long, shorten both of them // if both too long, shorten both of them
sourceName = StringUtilities.trimMiddle(sourceName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH); sourceName = StringUtilities.trimMiddle(sourceName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH);
destinationName = StringUtilities.trimMiddle(destinationName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH); destinationName =
StringUtilities.trimMiddle(destinationName, VTSESSION_NAME_PROGRAM_NAME_MAX_LENGTH);
return "VT_" + sourceName + "_" + destinationName; return "VT_" + sourceName + "_" + destinationName;
} }
@ -418,16 +406,17 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
state.put(VTWizardStateKey.NEW_SESSION_FOLDER, folder); state.put(VTWizardStateKey.NEW_SESSION_FOLDER, folder);
} }
private void openProgram(ProgramInfo programInfo) { private boolean openProgram(ProgramInfo programInfo) {
if (programInfo.hasProgram()) { if (programInfo.hasProgram()) {
return; // already open return true; // already open
} }
OpenProgramTask openProgramTask = new OpenProgramTask(programInfo.getFile(), tool); OpenProgramTask openProgramTask = new OpenProgramTask(programInfo.getFile(), tool);
new TaskLauncher(openProgramTask, tool.getActiveWindow()); new TaskLauncher(openProgramTask, tool.getActiveWindow());
OpenProgramRequest openProgram = openProgramTask.getOpenProgram(); OpenProgramRequest openProgram = openProgramTask.getOpenProgram();
programInfo.setProgram(openProgram != null ? openProgram.getProgram() : null); programInfo.setProgram(openProgram != null ? openProgram.getProgram() : null);
return programInfo.hasProgram();
} }
@Override @Override
@ -480,19 +469,25 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
DomainFile file = folder.getFile(name); DomainFile file = folder.getFile(name);
if (file != null) { if (file != null) {
notifyListenersOfStatusMessage( notifyListenersOfStatusMessage(
"'" + file.getPathname() + "' is the name of an existing domain file"); "'" + file.getPathname() + "' is the name of an existing project file");
return false; return false;
} }
openProgram(sourceProgramInfo); // Known Issue: Opening programs before comitted to using them (i.e., Next is clicked) seems
if (!sourceProgramInfo.hasProgram()) { // premature and will subject user to prompts about possible checkout and/or upgrades
// with possible slow re-disassembly (see GP-4151)
if (!isValidDestinationProgramFile() || !isValidSourceProgramFile()) {
return false;
}
if (!openProgram(sourceProgramInfo)) {
notifyListenersOfStatusMessage( notifyListenersOfStatusMessage(
"Can't open source program " + sourceProgramInfo.getName()); "Can't open source program " + sourceProgramInfo.getName());
return false; return false;
} }
openProgram(destinationProgramInfo); if (!openProgram(destinationProgramInfo)) {
if (!destinationProgramInfo.hasProgram()) {
notifyListenersOfStatusMessage( notifyListenersOfStatusMessage(
"Can't open destination program " + destinationProgramInfo.getName()); "Can't open destination program " + destinationProgramInfo.getName());
return false; return false;
@ -502,6 +497,29 @@ public class NewSessionPanel extends AbstractMageJPanel<VTWizardStateKey> {
return true; return true;
} }
private boolean isValidSourceProgramFile() {
try {
VTSessionFileUtil.validateSourceProgramFile(sourceProgramInfo.file, false);
}
catch (Exception e) {
notifyListenersOfStatusMessage(e.getMessage());
return false;
}
return true;
}
private boolean isValidDestinationProgramFile() {
try {
VTSessionFileUtil.validateDestinationProgramFile(destinationProgramInfo.file, false,
false);
}
catch (Exception e) {
notifyListenersOfStatusMessage(e.getMessage());
return false;
}
return true;
}
@Override @Override
public void addDependencies(WizardState<VTWizardStateKey> state) { public void addDependencies(WizardState<VTWizardStateKey> state) {
// none // none

View file

@ -44,8 +44,7 @@ public class VTNewSessionWizardManager extends AbstractMagePanelManager<VTWizard
@Override @Override
protected List<MagePanel<VTWizardStateKey>> createPanels() { protected List<MagePanel<VTWizardStateKey>> createPanels() {
List<MagePanel<VTWizardStateKey>> panels = List<MagePanel<VTWizardStateKey>> panels = new ArrayList<>();
new ArrayList<>();
panels.add(new NewSessionPanel(controller.getTool())); panels.add(new NewSessionPanel(controller.getTool()));
panels.add(new PreconditionsPanel(this)); panels.add(new PreconditionsPanel(this));
panels.add(new SummaryPanel()); panels.add(new SummaryPanel());

View file

@ -115,8 +115,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionNoSelectionUnlimitedAddresses() throws Exception { public void testAddToSessionNoSelectionUnlimitedAddresses() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -170,8 +169,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionNoSelectionLimitAddressesToEntireProgram() throws Exception { public void testAddToSessionNoSelectionLimitAddressesToEntireProgram() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -231,8 +229,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionNoSelectionLimitAddressesToMyOwn() throws Exception { public void testAddToSessionNoSelectionLimitAddressesToMyOwn() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -292,8 +289,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionNoSelectionLimitAddressesToMyOwnChanged() throws Exception { public void testAddToSessionNoSelectionLimitAddressesToMyOwnChanged() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -366,8 +362,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionWithSelectionLimitAddressesToEntireProgram() throws Exception { public void testAddToSessionWithSelectionLimitAddressesToEntireProgram() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -429,8 +424,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionWithSelectionLimitAddressesToSelection() throws Exception { public void testAddToSessionWithSelectionLimitAddressesToSelection() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -492,8 +486,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionWithSelectionLimitAddressesToMyOwn() throws Exception { public void testAddToSessionWithSelectionLimitAddressesToMyOwn() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -568,8 +561,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testAddToSessionWithSelectionLimitAddressesToMyOwnThenBackNext() throws Exception { public void testAddToSessionWithSelectionLimitAddressesToMyOwnThenBackNext() throws Exception {
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";
@ -671,8 +663,7 @@ public class VTAddToSessionTest extends AbstractGhidraHeadedIntegrationTest {
public void testAddToSessionResultingInNoMatchesFound() throws Exception { public void testAddToSessionResultingInNoMatchesFound() throws Exception {
setErrorGUIEnabled(true); setErrorGUIEnabled(true);
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
String sessionName = "Untitled"; String sessionName = "Untitled";

View file

@ -78,8 +78,7 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
plugin = getPlugin(tool, VTPlugin.class); plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin); controller = new VTControllerImpl(plugin);
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session)); runSwing(() -> controller.openVersionTrackingSession(session));

View file

@ -81,8 +81,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
VTPlugin plugin = getPlugin(tool, VTPlugin.class); VTPlugin plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin); controller = new VTControllerImpl(plugin);
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session)); runSwing(() -> controller.openVersionTrackingSession(session));
@ -390,8 +389,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
} }
@Test @Test
public void testApplyMatch_ReplaceSignature_CustomSourceAndDest() public void testApplyMatch_ReplaceSignature_CustomSourceAndDest() throws Exception {
throws Exception {
useMatch("0x00401040", "0x00401040"); useMatch("0x00401040", "0x00401040");
@ -442,8 +440,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
} }
@Test @Test
public void testApplyMatch_ReplaceSignature_NormalSourceCustomDest() public void testApplyMatch_ReplaceSignature_NormalSourceCustomDest() throws Exception {
throws Exception {
useMatch("0x00401040", "0x00401040"); useMatch("0x00401040", "0x00401040");
@ -666,8 +663,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
env.release(destinationProgram); env.release(destinationProgram);
destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session)); runSwing(() -> controller.openVersionTrackingSession(session));

View file

@ -94,8 +94,7 @@ public class VTMatchApplyTest extends AbstractGhidraHeadedIntegrationTest {
plugin = getPlugin(tool, VTPlugin.class); plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin); controller = new VTControllerImpl(plugin);
session = session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session)); runSwing(() -> controller.openVersionTrackingSession(session));

View file

@ -79,8 +79,7 @@ public abstract class AbstractCorrelatorTest extends AbstractGhidraHeadedIntegra
protected void exerciseFunctionsForFactory(final VTProgramCorrelatorFactory factory, protected void exerciseFunctionsForFactory(final VTProgramCorrelatorFactory factory,
AddressSetView sourceSetThatShouldBeFound) throws Exception { AddressSetView sourceSetThatShouldBeFound) throws Exception {
String name = factory.getName(); String name = factory.getName();
VTSession session = VTSession session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
try { try {
int sessionTransaction = session.startTransaction(name); int sessionTransaction = session.startTransaction(name);
@ -145,8 +144,7 @@ public abstract class AbstractCorrelatorTest extends AbstractGhidraHeadedIntegra
protected void exercisePreciseMatchesForFactory(VTProgramCorrelatorFactory factory, protected void exercisePreciseMatchesForFactory(VTProgramCorrelatorFactory factory,
Map<Address, Address> map) throws Exception { Map<Address, Address> map) throws Exception {
String name = factory.getName(); String name = factory.getName();
VTSession session = VTSession session = new VTSessionDB(name, sourceProgram, destinationProgram, this);
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
try { try {
int sessionTransaction = session.startTransaction(name); int sessionTransaction = session.startTransaction(name);

View file

@ -348,7 +348,7 @@ public abstract class AbstractVTMarkupItemTest extends AbstractGhidraHeadedInteg
} }
protected VTSessionDB createNewSession() throws Exception { protected VTSessionDB createNewSession() throws Exception {
return VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager", return new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
sourceProgram, destinationProgram, this); sourceProgram, destinationProgram, this);
} }

View file

@ -81,8 +81,7 @@ public class VTBaseTestCase extends AbstractGenericTest {
} }
public VTSessionDB createVTSession() throws IOException { public VTSessionDB createVTSession() throws IOException {
return VTSessionDB.createVTSession("Test DB", sourceProgram, destinationProgram, return new VTSessionDB("Test DB", sourceProgram, destinationProgram, VTTestUtils.class);
VTTestUtils.class);
} }
public static int getRandomInt() { public static int getRandomInt() {

View file

@ -69,7 +69,7 @@ public class VTTestEnv extends TestEnv {
sourceProgram = getProgram(sourceProgramName); sourceProgram = getProgram(sourceProgramName);
destinationProgram = getProgram(destinationProgramName); destinationProgram = getProgram(destinationProgramName);
session = VTSessionDB.createVTSession("Test", sourceProgram, destinationProgram, getTool()); session = new VTSessionDB("Test", sourceProgram, destinationProgram, getTool());
VTProgramCorrelator correlator = factory.createCorrelator(sourceProgram, VTProgramCorrelator correlator = factory.createCorrelator(sourceProgram,
sourceProgram.getMemory(), destinationProgram, destinationProgram.getMemory(), null); sourceProgram.getMemory(), destinationProgram, destinationProgram.getMemory(), null);
@ -111,7 +111,7 @@ public class VTTestEnv extends TestEnv {
} }
private VTSessionDB createAndOpenVTSession() throws IOException { private VTSessionDB createAndOpenVTSession() throws IOException {
session = VTSessionDB.createVTSession("Test", sourceProgram, destinationProgram, getTool()); session = new VTSessionDB("Test", sourceProgram, destinationProgram, getTool());
runSwing(() -> controller.openVersionTrackingSession(session), false); runSwing(() -> controller.openVersionTrackingSession(session), false);

View file

@ -53,7 +53,7 @@ public class StubVTController implements VTController {
} }
@Override @Override
public void openVersionTrackingSession(DomainFile domainFile) { public boolean openVersionTrackingSession(DomainFile domainFile) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -50,8 +50,8 @@ public abstract class Transaction implements AutoCloseable {
/** /**
* End this transaction if currently active. * End this transaction if currently active.
* @param commit true if changes shuold be commited, false if all changes in this transaction * @param commit true if changes should be commited, false if all changes in this transaction
* shuold be discarded (i.e., rollback). If this is a "sub-transaction" and commit is false, * should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is false,
* the larger transaction will rollback upon completion. * the larger transaction will rollback upon completion.
* @return true if changes have been commited or false if nothing to commit or commit parameter * @return true if changes have been commited or false if nothing to commit or commit parameter
* was specified as false. * was specified as false.

View file

@ -60,7 +60,7 @@ public class DomainFileProxy implements DomainFile {
} }
DomainFileProxy(String name, String parentPath, DomainObjectAdapter doa, int version, DomainFileProxy(String name, String parentPath, DomainObjectAdapter doa, int version,
String fileID, ProjectLocator projectLocation) { String fileID, ProjectLocator projectLocation) throws IOException {
this(name, doa); this(name, doa);
this.parentPath = parentPath; this.parentPath = parentPath;

View file

@ -94,7 +94,6 @@ public abstract class DomainObjectAdapter implements DomainObject {
consumers = new ArrayList<Object>(); consumers = new ArrayList<Object>();
consumers.add(consumer); consumers.add(consumer);
if (!UserData.class.isAssignableFrom(getClass())) { if (!UserData.class.isAssignableFrom(getClass())) {
// UserData instances do not utilize DomainFile storage
domainFile = new DomainFileProxy(name, this); domainFile = new DomainFileProxy(name, this);
} }
} }
@ -185,7 +184,12 @@ public abstract class DomainObjectAdapter implements DomainObject {
return temporary; return temporary;
} }
protected void setDomainFile(DomainFile df) { /**
* Set the {@link DomainFile} associated with this instance.
* @param df domain file
* @throws DomainObjectException if a severe failure occurs during the operation.
*/
protected void setDomainFile(DomainFile df) throws DomainObjectException {
if (df == null) { if (df == null) {
throw new IllegalArgumentException("DomainFile must not be null"); throw new IllegalArgumentException("DomainFile must not be null");
} }
@ -197,7 +201,6 @@ public abstract class DomainObjectAdapter implements DomainObject {
domainFile = df; domainFile = df;
fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.FILE_CHANGED, oldDf, df)); fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.FILE_CHANGED, oldDf, df));
fileChangeListeners.invoke().domainFileChanged(this); fileChangeListeners.invoke().domainFileChanged(this);
} }
protected void close() { protected void close() {

View file

@ -528,6 +528,9 @@ public class GhidraFileData {
projectData.clearDomainObject(getPathname()); projectData.clearDomainObject(getPathname());
// generate IOException // generate IOException
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause == null) {
cause = e;
}
if (cause instanceof IOException) { if (cause instanceof IOException) {
throw (IOException) cause; throw (IOException) cause;
} }
@ -831,9 +834,12 @@ public class GhidraFileData {
} }
/** /**
* Returns whether the object is read-only. From a framework point of view a read-only object * Returns whether this file is explicitly marked as read-only. This method is only supported
* can never be changed. * by the local file system and does not apply to a versioned file that is not checked-out.
* @return true if read-only * A versioned file that is not checked-out will always return false, while a
* {@link DomainFileProxy} will always return true.
* From a framework point of view a read-only file can never be changed.
* @return true if this file is marked read-only
*/ */
boolean isReadOnly() { boolean isReadOnly() {
synchronized (fileSystem) { synchronized (fileSystem) {

View file

@ -158,7 +158,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
toolFrame.addWindowListener(windowListener); toolFrame.addWindowListener(windowListener);
AppInfo.setFrontEndTool(this); AppInfo.setFrontEndTool(this);
AppInfo.setActiveProject(getProject());
initFrontEndOptions(); initFrontEndOptions();
} }
@ -408,7 +407,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
configureToolAction.setEnabled(true); configureToolAction.setEnabled(true);
setProject(project); setProject(project);
AppInfo.setActiveProject(project);
plugin.setActiveProject(project); plugin.setActiveProject(project);
firePluginEvent(new ProjectPluginEvent(getClass().getSimpleName(), project)); firePluginEvent(new ProjectPluginEvent(getClass().getSimpleName(), project));
} }
@ -616,7 +614,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
// Treat setVisible(false) as a dispose, as this is the only time we should be hidden // Treat setVisible(false) as a dispose, as this is the only time we should be hidden
AppInfo.setFrontEndTool(null); AppInfo.setFrontEndTool(null);
AppInfo.setActiveProject(null);
dispose(); dispose();
} }
} }
@ -645,9 +642,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
return isConfigurable(); return isConfigurable();
} }
}; };
MenuData menuData = MenuData menuData = new MenuData(
new MenuData(new String[] { ToolConstants.MENU_FILE, "Install Extensions" }, null, new String[] { ToolConstants.MENU_FILE, "Install Extensions" }, null, CONFIGURE_GROUP);
CONFIGURE_GROUP);
menuData.setMenuSubGroup(CONFIGURE_GROUP + 2); menuData.setMenuSubGroup(CONFIGURE_GROUP + 2);
installExtensionsAction.setMenuBarData(menuData); installExtensionsAction.setMenuBarData(menuData);

View file

@ -331,9 +331,12 @@ public interface DomainFile extends Comparable<DomainFile> {
public void setReadOnly(boolean state) throws IOException; public void setReadOnly(boolean state) throws IOException;
/** /**
* Returns whether the object is read-only. From a framework point of view a read-only object * Returns whether this file is explicitly marked as read-only. This method is only supported
* can never be changed. * by the local file system and does not apply to a versioned file that is not checked-out.
* @return true if read-only * A versioned file that is not checked-out will always return false, while a
* {@link DomainFileProxy} will always return true.
* From a framework point of view a read-only file can never be changed.
* @return true if this file is marked read-only
*/ */
public boolean isReadOnly(); public boolean isReadOnly();

View file

@ -27,6 +27,7 @@ import org.jdom.output.XMLOutputter;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.DefaultProjectData; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.data.TransientDataManager; import ghidra.framework.data.TransientDataManager;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.project.tool.GhidraToolTemplate; import ghidra.framework.project.tool.GhidraToolTemplate;
@ -291,16 +292,16 @@ public class DefaultProject implements Project {
throw new IOException("Invalid Ghidra URL specified: " + url); throw new IOException("Invalid Ghidra URL specified: " + url);
} }
ProjectData projectData = otherViewsMap.get(url); ProjectData viewedProjectData = otherViewsMap.get(url);
if (projectData == null) { if (viewedProjectData == null) {
projectData = openProjectView(url); viewedProjectData = openProjectView(url);
} }
if (projectData != null && visible && visibleViews.add(url)) { if (viewedProjectData != null && visible && visibleViews.add(url)) {
notifyVisibleViewAdded(url); notifyVisibleViewAdded(url);
} }
return projectData; return viewedProjectData;
} }
} }
@ -378,6 +379,11 @@ public class DefaultProject implements Project {
synchronized (otherViewsMap) { synchronized (otherViewsMap) {
isClosed = true; isClosed = true;
// Clear active project if this is the current active project.
if (AppInfo.getActiveProject() == this) {
AppInfo.setActiveProject(null);
}
for (DefaultProjectData dataMgr : otherViewsMap.values()) { for (DefaultProjectData dataMgr : otherViewsMap.values()) {
if (dataMgr != null) { if (dataMgr != null) {
dataMgr.close(); dataMgr.close();

View file

@ -28,6 +28,7 @@ import ghidra.framework.GenericRunInfo;
import ghidra.framework.ToolUtils; import ghidra.framework.ToolUtils;
import ghidra.framework.client.*; import ghidra.framework.client.*;
import ghidra.framework.data.TransientDataManager; import ghidra.framework.data.TransientDataManager;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
@ -111,6 +112,8 @@ public class DefaultProjectManager implements ProjectManager {
lastOpenedProject = projectLocator; lastOpenedProject = projectLocator;
updatePreferences(); updatePreferences();
} }
AppInfo.setActiveProject(currentProject);
return currentProject; return currentProject;
} }
@ -138,6 +141,7 @@ public class DefaultProjectManager implements ProjectManager {
try { try {
currentProject = new DefaultProject(this, projectLocator, resetOwner); currentProject = new DefaultProject(this, projectLocator, resetOwner);
AppInfo.setActiveProject(currentProject);
if (doRestore) { if (doRestore) {
currentProject.restore(); currentProject.restore();
} }
@ -166,6 +170,7 @@ public class DefaultProjectManager implements ProjectManager {
} }
} }
} }
AppInfo.setActiveProject(null);
return null; return null;
} }