GP-3697 Added delayed ProjectFileManager disposal in support of URL use

and opening linked project files and renamed ProjectFileData to
DefaultProjectData.
This commit is contained in:
ghidra1
2023-08-11 12:49:19 -04:00
parent 5ef4b269a1
commit 3eb642885c
51 changed files with 1636 additions and 813 deletions
@@ -23,7 +23,7 @@ import org.jdom.Element;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@@ -664,7 +664,7 @@ public class DebuggerCoordinates {
if (projData == null) { if (projData == null) {
try { try {
// FIXME! orphaned instance - transient in nature // FIXME! orphaned instance - transient in nature
projData = new ProjectFileManager(projLoc, false, false); projData = new DefaultProjectData(projLoc, false, false);
} }
catch (NotOwnerException e) { catch (NotOwnerException e) {
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed", Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed",
@@ -25,6 +25,7 @@ import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator; import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectClosedListener; import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.trace.model.*; import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
@@ -45,7 +46,7 @@ public class ObjectTreeModel implements DisplaysModified {
} }
@Override @Override
public void domainObjectClosed() { public void domainObjectClosed(DomainObject dobj) {
setTrace(null); setTrace(null);
} }
@@ -94,7 +94,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
} }
@Override @Override
public void domainObjectClosed() { public void domainObjectClosed(DomainObject dobj) {
// assume dobj == program
dispose(); dispose();
} }
@@ -353,9 +354,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
* trace, or bogus external libraries in a mapped program, scoring libraries before module * trace, or bogus external libraries in a mapped program, scoring libraries before module
* names should not cause problems. * names should not cause problems.
*/ */
Comparator<IndexEntry> comparator = byIsLibrary Comparator<IndexEntry> comparator =
.thenComparing(byNameSource) byIsLibrary.thenComparing(byNameSource).thenComparing(byFolderUses);
.thenComparing(byFolderUses);
return projectData.getFileByID(entries.stream().max(comparator).get().dfID); return projectData.getFileByID(entries.stream().max(comparator).get().dfID);
} }
@@ -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.
@@ -14,16 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import java.io.IOException;
import java.lang.reflect.Method;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.Project; import ghidra.framework.model.Project;
import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystem;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.local.LocalFolderItem; import ghidra.framework.store.local.LocalFolderItem;
import java.io.IOException;
import java.lang.reflect.Method;
public class CleanupMergeDatabasesScript extends GhidraScript { public class CleanupMergeDatabasesScript extends GhidraScript {
@Override @Override
@@ -31,8 +30,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
Project project = state.getProject(); Project project = state.getProject();
ProjectFileManager fileMgr = (ProjectFileManager) project.getProjectData(); DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
LocalFileSystem fs = (LocalFileSystem) fileMgr.getPrivateFileSystem(); LocalFileSystem fs = (LocalFileSystem) projectData.getPrivateFileSystem();
int cnt = cleanupFolder(fs, "/"); int cnt = cleanupFolder(fs, "/");
@@ -61,9 +60,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
} }
// fs.getItemNames(folderPath, true) // fs.getItemNames(folderPath, true)
String[] itemNames = String[] itemNames = (String[]) invokeInstanceMethod("getItemNames", fs,
(String[]) invokeInstanceMethod("getItemNames", fs, new Class[] { String.class, new Class[] { String.class, boolean.class }, new Object[] { folderPath, true });
boolean.class }, new Object[] { folderPath, true });
for (String itemName : itemNames) { for (String itemName : itemNames) {
if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) { if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) {
@@ -78,8 +76,9 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
else { else {
// make sure we get item out of index // make sure we get item out of index
//fs.deallocateItemStorage(folderPath, itemName); //fs.deallocateItemStorage(folderPath, itemName);
invokeInstanceMethod("deallocateItemStorage", fs, new Class[] { String.class, invokeInstanceMethod("deallocateItemStorage", fs,
String.class }, new Object[] { folderPath, itemName }); new Class[] { String.class, String.class },
new Object[] { folderPath, itemName });
} }
++cnt; ++cnt;
} }
@@ -75,7 +75,16 @@ public class AskScript extends GhidraScript {
} }
Program prog = askProgram("Please choose a program to open."); Program prog = askProgram("Please choose a program to open.");
println("Program picked: " + prog.getName()); if (prog != null) {
// NOTE: if prog is not null script must release it when done using.
// This may also be accomplished via an overridden cleanup(boolean) method.
try {
println("Program picked: " + prog.getName());
}
finally {
prog.release(this); // will remain open in tool if applicable
}
}
DomainFile domFile = askDomainFile("Which domain file would you like?"); DomainFile domFile = askDomainFile("Which domain file would you like?");
println("Domain file: " + domFile.getName()); println("Domain file: " + domFile.getName());
@@ -38,19 +38,24 @@ public class CompareAnalysisScript extends GhidraScript {
if (otherProgram == null) { if (otherProgram == null) {
return; return;
} }
println("\n\n****** COMPARING FUNCTIONS:\n"); try {
compareFunctions(otherProgram); println("\n\n****** COMPARING FUNCTIONS:\n");
println("\n\n****** COMPARING STRINGS:\n"); compareFunctions(otherProgram);
compareStrings(otherProgram); println("\n\n****** COMPARING STRINGS:\n");
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n"); compareStrings(otherProgram);
reportPercentDisassembled(currentProgram); println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
reportPercentDisassembled(otherProgram); reportPercentDisassembled(currentProgram);
println("\n\n****** COMPARING SWITCH TABLES:\n"); reportPercentDisassembled(otherProgram);
compareSwitchTables(otherProgram); println("\n\n****** COMPARING SWITCH TABLES:\n");
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n"); compareSwitchTables(otherProgram);
compareNoReturns(otherProgram); println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
println("\n\n****** COMPARING ERRORS:\n"); compareNoReturns(otherProgram);
compareErrors(otherProgram); println("\n\n****** COMPARING ERRORS:\n");
compareErrors(otherProgram);
}
finally {
otherProgram.release(this);
}
} }
void compareFunctions(Program otherProgram) { void compareFunctions(Program otherProgram) {
@@ -73,7 +73,7 @@ public class AnalysisStateInfo {
if (stateMap == null) { if (stateMap == null) {
stateMap = new HashMap<>(); stateMap = new HashMap<>();
programStates.put(program, stateMap); programStates.put(program, stateMap);
program.addCloseListener(() -> programStates.remove(program)); program.addCloseListener(doa -> programStates.remove(program));
} }
stateMap.put(state.getClass(), state); stateMap.put(state.getClass(), state);
} }
@@ -57,7 +57,7 @@ import ghidra.util.task.*;
* Provides support for auto analysis tasks. * Provides support for auto analysis tasks.
* Manages a pipeline or priority of tasks to run given some event has occurred. * Manages a pipeline or priority of tasks to run given some event has occurred.
*/ */
public class AutoAnalysisManager implements DomainObjectListener, DomainObjectClosedListener { public class AutoAnalysisManager implements DomainObjectListener {
/** /**
* The name of the shared thread pool that analyzers can uses to do parallel processing. * The name of the shared thread pool that analyzers can uses to do parallel processing.
@@ -145,7 +145,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
private AutoAnalysisManager(Program program) { private AutoAnalysisManager(Program program) {
this.program = program; this.program = program;
eventQueueID = program.createPrivateEventQueue(this, 500); eventQueueID = program.createPrivateEventQueue(this, 500);
program.addCloseListener(this); program.addCloseListener(dobj -> dispose());
initializeAnalyzers(); initializeAnalyzers();
} }
@@ -361,11 +361,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
subType == ChangeManager.FUNCTION_CHANGED_RETURN; subType == ChangeManager.FUNCTION_CHANGED_RETURN;
} }
@Override
public void domainObjectClosed() {
dispose();
}
@Override @Override
public void domainObjectChanged(DomainObjectChangedEvent ev) { public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (program == null) { if (program == null) {
@@ -961,10 +956,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
} }
PluginTool anyTool = null; PluginTool anyTool = null;
Iterator<PluginTool> iterator = toolSet.iterator(); for (PluginTool tool : toolSet) {
while (iterator.hasNext()) {
PluginTool tool = iterator.next();
anyTool = tool; anyTool = tool;
JFrame toolFrame = tool.getToolFrame(); JFrame toolFrame = tool.getToolFrame();
if (toolFrame != null && toolFrame.isActive()) { if (toolFrame != null && toolFrame.isActive()) {
@@ -33,7 +33,6 @@ import ghidra.app.nav.Navigatable;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.*; import ghidra.app.util.viewer.listingpanel.*;
import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@@ -510,30 +509,19 @@ public class MarkerManager implements MarkerService {
private final AddressColorCache colorCache = new AddressColorCache(); private final AddressColorCache colorCache = new AddressColorCache();
private final ColorBlender blender = new ColorBlender(); private final ColorBlender blender = new ColorBlender();
private final MarkerSetCache cache;
private final Program program;
private final DomainObjectClosedListener closeListener = this::programClosed;
public MarkerSetCacheEntry(MarkerSetCache cache, Program program) { public MarkerSetCacheEntry(MarkerSetCache cache, Program program) {
this.cache = cache;
this.program = program;
/** /**
* Use this close listener approach instead of plugin events, since we don't get a * Use this close listener approach instead of plugin events, since we don't get a
* ProgramClosedPluginEvent when a trace view is closed, but we can listen for its * ProgramClosedPluginEvent when a trace view is closed, but we can listen for its
* domain object closing, which works for plain programs, too. * domain object closing, which works for plain programs, too.
*/ */
program.addCloseListener(closeListener); program.addCloseListener(dobj -> cache.programClosed(program));
} }
void clearColors() { void clearColors() {
colorCache.clear(); colorCache.clear();
} }
private void programClosed() {
program.removeCloseListener(closeListener);
cache.programClosed(program);
}
MarkerSetImpl getByName(String name) { MarkerSetImpl getByName(String name) {
for (MarkerSetImpl set : markerSets) { for (MarkerSetImpl set : markerSets) {
if (name.equals(set.getName())) { if (name.equals(set.getName())) {
@@ -1883,6 +1883,9 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @param transformer the function to turn a String into a T * @param transformer the function to turn a String into a T
* @param key the values used to create a key for lookup in the script properties file * @param key the values used to create a key for lookup in the script properties file
* @return null if no value was found in the aforementioned sources * @return null if no value was found in the aforementioned sources
* @throws IllegalArgumentException if the loaded String value cannot be parsed into a
* <code>T</code> or property not defined when in headless
* mode.
*/ */
private <T> T loadAskValue(StringTransformer<T> transformer, String key) { private <T> T loadAskValue(StringTransformer<T> transformer, String key) {
T value = loadAskValue(null, transformer, key); T value = loadAskValue(null, transformer, key);
@@ -1897,11 +1900,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @param defaultValue an optional default value that will be used if no suitable * @param defaultValue an optional default value that will be used if no suitable
* value can be found in script args or a properties file * value can be found in script args or a properties file
* @param transformer the function to turn a String into a T * @param transformer the function to turn a String into a T
* @param key the values used to create a key for lookup in the script properties file * @param key the value property key used for lookup in the script properties file
* @return null if no value was found in the aforementioned sources * @return null if no value was found in the aforementioned sources
* *
* @throws IllegalArgumentException if the loaded String value cannot be parsed into a * @throws IllegalArgumentException if the loaded String value cannot be parsed into a
* <code>T</code>. * <code>T</code> or property not defined when in headless
* mode and no defaultValue has been specified.
*/ */
private <T> T loadAskValue(T defaultValue, StringTransformer<T> transformer, String key) { private <T> T loadAskValue(T defaultValue, StringTransformer<T> transformer, String key) {
@@ -2513,7 +2517,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
public Address askAddress(String title, String message) throws CancelledException { public Address askAddress(String title, String message) throws CancelledException {
return askAddress(title, message, null); return askAddress(title, message, null);
} }
/** /**
* Returns an Address, using the String parameters for guidance. The actual behavior of the * Returns an Address, using the String parameters for guidance. The actual behavior of the
* method depends on your environment, which can be GUI or headless. * method depends on your environment, which can be GUI or headless.
@@ -2550,15 +2554,16 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address * @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address
* specified in the .properties file * specified in the .properties file
*/ */
public Address askAddress(String title, String message, String defaultValue) throws CancelledException { public Address askAddress(String title, String message, String defaultValue)
throws CancelledException {
String key = join(title, message); String key = join(title, message);
Address defaultAddr = null; Address defaultAddr = null;
if (defaultValue != null) { if (defaultValue != null) {
defaultAddr = currentProgram.getAddressFactory().getAddress(defaultValue); defaultAddr = currentProgram.getAddressFactory().getAddress(defaultValue);
} }
// if defaultAddr is null then it assumes no default value // if defaultAddr is null then it assumes no default value
Address existingValue = loadAskValue(defaultAddr, this::parseAddress, key); Address existingValue = loadAskValue(defaultAddr, this::parseAddress, key);
if (isRunningHeadless()) { if (isRunningHeadless()) {
@@ -2683,7 +2688,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
* *
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in * @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
* headless mode) * headless mode)
* @return the user-specified Program * @return the user-selected Program with this script as the consumer or null if a program was
* not selected. NOTE: It is very important that the program instance returned by this method
* ALWAYS be properly released when no longer needed. The script which invoked this method must be
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
* properly release the program may result in improper project disposal. If the program was
* opened by the tool, the tool will be a second consumer responsible for its own release.
* @throws VersionException if the Program is out-of-date from the version of GHIDRA * @throws VersionException if the Program is out-of-date from the version of GHIDRA
* @throws IOException if there is an error accessing the Program's DomainObject * @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled * @throws CancelledException if the operation is cancelled
@@ -2693,33 +2703,34 @@ public abstract class GhidraScript extends FlatProgramAPI {
public Program askProgram(String title) public Program askProgram(String title)
throws VersionException, IOException, CancelledException { throws VersionException, IOException, CancelledException {
DomainFile existingValue = loadAskValue(this::parseDomainFile, title); DomainFile choice = loadAskValue(this::parseDomainFile, title);
if (isRunningHeadless()) { if (!isRunningHeadless()) {
return (Program) existingValue.getDomainObject(this, false, false, monitor); choice = doAsk(Program.class, title, "", choice, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
}
return dtd.getDomainFile();
});
} }
DomainFile choice = doAsk(Program.class, title, "", existingValue, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
}
return dtd.getDomainFile();
});
if (choice == null) { if (choice == null) {
return null; return null;
} }
Program p = (Program) choice.getDomainObject(this, false, false, monitor);
PluginTool tool = state.getTool(); PluginTool tool = state.getTool();
if (tool == null) { if (tool == null) {
return (Program) choice.getDomainObject(this, false, false, monitor); return p;
} }
ProgramManager pm = tool.getService(ProgramManager.class); ProgramManager pm = tool.getService(ProgramManager.class);
return pm.openProgram(choice); pm.openProgram(p);
return p;
} }
/** /**
@@ -2768,10 +2779,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
* *
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in headless * @param title the title of the pop-up dialog (in GUI mode) or the variable name (in headless
* mode or when using .properties file) * mode or when using .properties file)
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid domain
* file specified in the .properties file
* @return the user-selected domain file * @return the user-selected domain file
* @throws CancelledException if the operation is cancelled * @throws CancelledException if the operation is cancelled
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid domain
* file specified in the .properties file
*/ */
public DomainFile askDomainFile(String title) throws CancelledException { public DomainFile askDomainFile(String title) throws CancelledException {
@@ -3015,8 +3026,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
throw new ImproperUseException( throw new ImproperUseException(
"The askPassword() method can only be used when running headed Ghidra."); "The askPassword() method can only be used when running headed Ghidra.");
} }
PasswordDialog dialog = PasswordDialog dialog = new PasswordDialog(title, null, null, prompt, null, null);
new PasswordDialog(title, null, null, prompt, null, null);
try { try {
state.getTool().showDialog(dialog); state.getTool().showDialog(dialog);
if (!dialog.okWasPressed()) { if (!dialog.okWasPressed()) {
@@ -878,7 +878,7 @@ public class HeadlessAnalyzer {
// Get parent folder to pass to GhidraScript // Get parent folder to pass to GhidraScript
File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI()) File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI())
.getParentFile(); .getParentFile();
currScript = (GhidraScript) c.getConstructor().newInstance(); currScript = (GhidraScript) c.getConstructor().newInstance();
currScript.setScriptArgs(scriptArgs); currScript.setScriptArgs(scriptArgs);
@@ -1575,13 +1575,12 @@ public class HeadlessAnalyzer {
} }
else { else {
if (options.readOnly) { if (options.readOnly) {
Msg.info(this, "REPORT: Discarded file import due to readOnly option: " + Msg.info(this,
loaded); "REPORT: Discarded file import due to readOnly option: " + loaded);
} }
else { else {
Msg.info(this, Msg.info(this, "REPORT: Discarded file import as a result of script " +
"REPORT: Discarded file import as a result of script " + "activity or analysis timeout: " + loaded);
"activity or analysis timeout: " + loaded);
} }
} }
} }
@@ -1627,9 +1626,9 @@ public class HeadlessAnalyzer {
} }
} }
private LoadResults<Program> loadPrograms(File file, String folderPath) throws VersionException, private LoadResults<Program> loadPrograms(File file, String folderPath)
InvalidNameException, DuplicateNameException, CancelledException, IOException, throws VersionException, InvalidNameException, DuplicateNameException,
LoadException { CancelledException, IOException, LoadException {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
if (options.loaderClass == null) { if (options.loaderClass == null) {
@@ -28,6 +28,7 @@ import org.junit.*;
import generic.test.TestUtils; import generic.test.TestUtils;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FileSystemEventManager; import ghidra.framework.store.FileSystemEventManager;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
@@ -37,13 +38,13 @@ import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest { public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest {
private File privateProjectDir; private File privateProjectDir;
private File sharedProjectDir; private File sharedProjectDir;
private FileSystem sharedFS; private FileSystem sharedFS;
private LocalFileSystem privateFS; private LocalFileSystem privateFS;
private ProjectFileManager fileMgr; private DefaultProjectData projectData;
private DomainFolder root; private DomainFolder root;
private List<MyEvent> events = new ArrayList<>(); private List<MyEvent> events = new ArrayList<>();
@@ -83,9 +84,9 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false, sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
true, false, true); true, false, true);
fileMgr = new ProjectFileManager(privateFS, sharedFS); projectData = new DefaultProjectData(privateFS, sharedFS);
fileMgr.addDomainFolderChangeListener(new MyDomainFolderChangeListener()); projectData.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
root = fileMgr.getRootFolder(); root = projectData.getRootFolder();
flushFileSystemEventsAndClearTestQueue(); flushFileSystemEventsAndClearTestQueue();
} }
@@ -97,7 +98,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@After @After
public void tearDown() { public void tearDown() {
fileMgr.dispose(); projectData.dispose();
deleteAll(privateProjectDir); deleteAll(privateProjectDir);
deleteAll(sharedProjectDir); deleteAll(sharedProjectDir);
} }
@@ -136,9 +137,15 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
} }
} }
@Test
public void testGetLocalProjectURL() {
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator()),
projectData.getLocalProjectURL());
}
@Test @Test
public void testGetRootFolder() throws Exception { public void testGetRootFolder() throws Exception {
DomainFolder rootFolder = fileMgr.getRootFolder(); DomainFolder rootFolder = projectData.getRootFolder();
assertEquals("/", rootFolder.getPathname()); assertEquals("/", rootFolder.getPathname());
assertEquals(3, rootFolder.getFolders().length); assertEquals(3, rootFolder.getFolders().length);
} }
@@ -146,11 +153,11 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testGetFolder() throws Exception { public void testGetFolder() throws Exception {
DomainFolder rootFolder = fileMgr.getRootFolder(); DomainFolder rootFolder = projectData.getRootFolder();
DomainFolder df1 = fileMgr.getFolder("/"); DomainFolder df1 = projectData.getFolder("/");
DomainFolder df2 = fileMgr.getFolder("/a"); DomainFolder df2 = projectData.getFolder("/a");
DomainFolder df3 = fileMgr.getFolder("/a/y"); DomainFolder df3 = projectData.getFolder("/a/y");
DomainFolder df4 = fileMgr.getFolder("/a/x"); DomainFolder df4 = projectData.getFolder("/a/x");
assertNotNull(rootFolder); assertNotNull(rootFolder);
assertEquals(rootFolder, df1); assertEquals(rootFolder, df1);
@@ -178,7 +185,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testCreateFile() throws Exception { public void testCreateFile() throws Exception {
DomainFolder folder = fileMgr.getFolder("/a"); DomainFolder folder = projectData.getFolder("/a");
folder.getFiles(); // visit folder to receive change events from this folder folder.getFiles(); // visit folder to receive change events from this folder
flushFileSystemEventsAndClearTestQueue(); flushFileSystemEventsAndClearTestQueue();
@@ -195,18 +202,18 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
assertEventsSize(2); assertEventsSize(2);
checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null); checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null);
DomainFile df = fileMgr.getFileByID(fileID1); DomainFile df = projectData.getFileByID(fileID1);
assertNotNull(df); assertNotNull(df);
assertEquals("file1", df.getName()); assertEquals("file1", df.getName());
assertTrue(!df.isVersioned()); assertTrue(!df.isVersioned());
df = fileMgr.getFileByID(fileID2); df = projectData.getFileByID(fileID2);
assertNotNull(df2); assertNotNull(df2);
assertEquals("file2", df.getName()); assertEquals("file2", df.getName());
df1.addToVersionControl("", false, TaskMonitor.DUMMY); df1.addToVersionControl("", false, TaskMonitor.DUMMY);
df = fileMgr.getFileByID(fileID1); df = projectData.getFileByID(fileID1);
assertNotNull(df1); assertNotNull(df1);
assertEquals("file1", df.getName()); assertEquals("file1", df.getName());
assertTrue(df.isVersioned()); assertTrue(df.isVersioned());
@@ -216,7 +223,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testFileIndex() throws Exception { public void testFileIndex() throws Exception {
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", fileMgr); DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", projectData);
assertNotNull(fileIndex); assertNotNull(fileIndex);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -224,21 +231,21 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
(HashMap<String, String>) getInstanceField("fileIdToPathIndex", fileIndex); (HashMap<String, String>) getInstanceField("fileIdToPathIndex", fileIndex);
assertNotNull(fileIdToPathIndex); assertNotNull(fileIdToPathIndex);
DomainFolder folder = fileMgr.getFolder("/a"); DomainFolder folder = projectData.getFolder("/a");
DomainFile df1 = createFile(folder, "file1"); DomainFile df1 = createFile(folder, "file1");
String fileID = df1.getFileID(); String fileID = df1.getFileID();
assertEquals(df1, fileMgr.getFileByID(fileID)); assertEquals(df1, projectData.getFileByID(fileID));
// invalidate folder data to force search // invalidate folder data to force search
GhidraFolderData rootFolderData = fileMgr.getRootFolderData(); GhidraFolderData rootFolderData = projectData.getRootFolderData();
rootFolderData.dispose(); rootFolderData.dispose();
assertTrue(fileIdToPathIndex.isEmpty()); // folder invalidation should cause map to clear assertTrue(fileIdToPathIndex.isEmpty()); // folder invalidation should cause map to clear
assertEquals(df1, fileMgr.getFileByID(fileID)); assertEquals(df1, projectData.getFileByID(fileID));
assertFalse(fileIdToPathIndex.isEmpty()); // index should become populated assertFalse(fileIdToPathIndex.isEmpty()); // index should become populated
} }
@@ -246,7 +253,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testFileIndexUndoCheckout() throws Exception { public void testFileIndexUndoCheckout() throws Exception {
// TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition // TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition
DomainFolder folder = fileMgr.getFolder("/a"); DomainFolder folder = projectData.getFolder("/a");
DomainFile df1 = createFile(folder, "file1"); DomainFile df1 = createFile(folder, "file1");
String fileID = df1.getFileID(); String fileID = df1.getFileID();
@@ -265,7 +272,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testFileIndexHijack() throws Exception { public void testFileIndexHijack() throws Exception {
// TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition // TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition
DomainFolder folder = fileMgr.getFolder("/a"); DomainFolder folder = projectData.getFolder("/a");
folder.getFiles(); // visit folder to enable folder change listener folder.getFiles(); // visit folder to enable folder change listener
// create shared file /a/file1 and keep checked-out // create shared file /a/file1 and keep checked-out
@@ -285,7 +292,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
df1.setName("file2"); df1.setName("file2");
DomainFile df2 = fileMgr.getFile("/a/file2"); DomainFile df2 = projectData.getFile("/a/file2");
assertTrue(!fileID.equals(df2.getFileID())); assertTrue(!fileID.equals(df2.getFileID()));
@@ -451,7 +458,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testFolderRenamedEvent3() throws Exception { public void testFolderRenamedEvent3() throws Exception {
fileMgr.getFolder("/a"); // force folder refresh to reduce event count projectData.getFolder("/a"); // force folder refresh to reduce event count
flushFileSystemEventsAndClearTestQueue(); flushFileSystemEventsAndClearTestQueue();
// exists in localFS so "b" folder should not get created again // exists in localFS so "b" folder should not get created again
@@ -495,7 +502,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testRenameFolder6() throws Exception { public void testRenameFolder6() throws Exception {
DomainFolder aFolder = fileMgr.getFolder("/a"); DomainFolder aFolder = projectData.getFolder("/a");
assertNotNull(aFolder); assertNotNull(aFolder);
aFolder.getFolders(); // visit folder to receive change events for it aFolder.getFolders(); // visit folder to receive change events for it
@@ -601,12 +608,12 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
// versioned folder was moved to /c/a, but private folder /a should still exist // versioned folder was moved to /c/a, but private folder /a should still exist
GhidraFolder folder = (GhidraFolder) fileMgr.getFolder("/a"); GhidraFolder folder = (GhidraFolder) projectData.getFolder("/a");
assertNotNull(folder); assertNotNull(folder);
assertTrue(folder.privateExists()); assertTrue(folder.privateExists());
assertFalse(folder.sharedExists()); assertFalse(folder.sharedExists());
folder = (GhidraFolder) fileMgr.getFolder("/c/a"); folder = (GhidraFolder) projectData.getFolder("/c/a");
assertNotNull(folder); assertNotNull(folder);
assertFalse(folder.privateExists()); assertFalse(folder.privateExists());
assertTrue(folder.sharedExists()); assertTrue(folder.sharedExists());
@@ -41,7 +41,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
private FileSystem sharedFS; private FileSystem sharedFS;
private LocalFileSystem privateFS; private LocalFileSystem privateFS;
private ProjectFileManager pfm; private DefaultProjectData projectData;
private GhidraFolder root; private GhidraFolder root;
public GhidraFileTest() { public GhidraFileTest() {
@@ -76,8 +76,8 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
false, false, true); false, false, true);
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false, sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
true, false, true); true, false, true);
pfm = new ProjectFileManager(privateFS, sharedFS); projectData = new DefaultProjectData(privateFS, sharedFS);
root = pfm.getRootFolder(); root = projectData.getRootFolder();
} }
@@ -88,12 +88,12 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
} }
@Test @Test
public void testLocalURL() throws IOException { public void testGetLocalProjectURL() throws IOException {
createDB(privateFS, "/a", "file1"); createDB(privateFS, "/a", "file1");
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"), assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", "xyz"),
pfm.getFile("/a/file1").getLocalProjectURL("xyz")); projectData.getFile("/a/file1").getLocalProjectURL("xyz"));
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null), assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", null),
pfm.getFile("/a/file1").getLocalProjectURL(null)); projectData.getFile("/a/file1").getLocalProjectURL(null));
} }
@Test @Test
@@ -345,7 +345,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
private void refresh() throws IOException { private void refresh() throws IOException {
// refresh everything regardless of visited state // refresh everything regardless of visited state
pfm.refresh(true); projectData.refresh(true);
} }
private void deleteAll(File file) { private void deleteAll(File file) {
@@ -22,6 +22,8 @@ import java.io.IOException;
import org.junit.*; import org.junit.*;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -31,6 +33,7 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
private LocalFileSystem sharedFS; private LocalFileSystem sharedFS;
private LocalFileSystem privateFS; private LocalFileSystem privateFS;
private DefaultProjectData projectData;
private GhidraFolder root; private GhidraFolder root;
public GhidraFolderTest() { public GhidraFolderTest() {
@@ -68,8 +71,8 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
false, false, true); false, false, true);
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false, sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
true, false, true); true, false, true);
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS); projectData = new DefaultProjectData(privateFS, sharedFS);
root = projectFileManager.getRootFolder(); root = projectData.getRootFolder();
} }
private void deleteTestFiles() { private void deleteTestFiles() {
@@ -82,6 +85,15 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
deleteTestFiles(); deleteTestFiles();
} }
@Test
public void testGetLocalProjectURL() {
ProjectLocator projectLocator = projectData.getProjectLocator();
assertEquals(GhidraURL.makeURL(projectLocator, "/a/y", null),
projectData.getFolder("/a/y").getLocalProjectURL());
assertEquals(GhidraURL.makeURL(projectLocator, "/a/x", null),
projectData.getFolder("/a/x").getLocalProjectURL());
}
@Test @Test
public void testGetFolderNames() throws Exception { public void testGetFolderNames() throws Exception {
GhidraFolder[] folders = root.getFolders(); GhidraFolder[] folders = root.getFolders();
@@ -15,24 +15,23 @@
*/ */
package ghidra.framework.data; package ghidra.framework.data;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.Project;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.InvalidNameException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import org.junit.*; import org.junit.*;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.InvalidNameException;
public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegrationTest { public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegrationTest {
private File testRootDir; private File testRootDir;
private File privateProjectDir; private File privateProjectDir;
private File sharedProjectDir; private File sharedProjectDir;
private DomainFolder root; private DomainFolder root;
private Project project;
private LocalFileSystem sharedFS; private LocalFileSystem sharedFS;
private LocalFileSystem privateFS; private LocalFileSystem privateFS;
@@ -52,8 +51,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
true, false, false); true, false, false);
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true, sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true,
true, false, false); true, false, false);
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS); DefaultProjectData projectData = new DefaultProjectData(privateFS, sharedFS);
root = projectFileManager.getRootFolder(); root = projectData.getRootFolder();
} }
@After @After
@@ -68,8 +67,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
private void deleteAll(File file) { private void deleteAll(File file) {
if (file.isDirectory()) { if (file.isDirectory()) {
File[] files = file.listFiles(); File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) { for (File file2 : files) {
deleteAll(files[i]); deleteAll(file2);
} }
} }
file.delete(); file.delete();
@@ -903,8 +903,8 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
// //
//@formatter:off //@formatter:off
Object projectFileManager = getInstanceField("fileManager", df); Object projectData = getInstanceField("projectData", df);
invokeInstanceMethod("setDomainObject", projectFileManager, invokeInstanceMethod("setDomainObject", projectData,
new Class[] { String.class, DomainObjectAdapter.class }, new Class[] { String.class, DomainObjectAdapter.class },
new Object[] { path, program } new Object[] { path, program }
); );
@@ -962,8 +962,7 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
} }
} }
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, true);
true);
} }
} }
@@ -15,16 +15,18 @@
*/ */
package ghidra.framework.project; package ghidra.framework.project;
import static org.junit.Assert.*;
import java.net.URL; import java.net.URL;
import org.junit.*; import org.junit.*;
import generic.test.AbstractGenericTest; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.Project; import ghidra.framework.model.*;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.*;
import ghidra.test.ProjectTestUtils; import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/** /**
* Test class for adding and a view to a project, and removing * Test class for adding and a view to a project, and removing
@@ -32,7 +34,7 @@ import ghidra.test.ProjectTestUtils;
*/ */
public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest { public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest {
private final static String DIRECTORY_NAME = AbstractGenericTest.getTestDirectoryPath(); private final static String DIRECTORY_NAME = getTestDirectoryPath();
private final static String PROJECT_NAME1 = "TestAddViewToProject"; private final static String PROJECT_NAME1 = "TestAddViewToProject";
private final static String PROJECT_VIEW1 = "TestView1"; private final static String PROJECT_VIEW1 = "TestView1";
private final static String PROJECT_VIEW2 = "TestView2"; private final static String PROJECT_VIEW2 = "TestView2";
@@ -52,24 +54,9 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2); ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2);
} }
/**
* Do the test.
* @param args same args that are passed to RegressionTester.main()
*/
@Test @Test
public void testAddToView() throws Exception { public void testAddToView() throws Exception {
// String filename = System.getProperty("user.dir") +
// File.separator + "testGhidraPreferences";
//
// try {
// Preferences.load(filename);
//
// } catch (IOException e) {
// }
//
// Preferences.setFilename(filename);
// make sure we have projects to use as the project view... // make sure we have projects to use as the project view...
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close(); ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close();
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close(); ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close();
@@ -87,12 +74,12 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
// validate the view was added to project // validate the view was added to project
ProjectLocator[] projViews = project.getProjectViews(); ProjectLocator[] projViews = project.getProjectViews();
for (ProjectLocator projView : projViews) { for (ProjectLocator projView : projViews) {
System.out.println("added view: " + projView); Msg.debug(this, "** added view: " + projView);
} }
// remove the view... // remove the view...
project.removeProjectView(view); project.removeProjectView(view);
System.out.println("removed view: " + view); Msg.debug(this, "** removed view: " + view);
projViews = project.getProjectViews(); projViews = project.getProjectViews();
for (ProjectLocator projView : projViews) { for (ProjectLocator projView : projViews) {
@@ -106,4 +93,59 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
} }
} }
@Test
public void testCloseViewWithOpenProgram() throws Exception {
DomainObject dobj = null;
// make sure we have projects to use as the project view...
Project project = ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1);
try {
ToyProgramBuilder builder = new ToyProgramBuilder("Test", true);
DomainFolder rootFolder = project.getProjectData().getRootFolder();
rootFolder.createFile("Test", builder.getProgram(), TaskMonitor.DUMMY);
builder.dispose();
project.close();
// get project (create it if it doesn't exist...)
project = ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_NAME1);
URL view = GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW1);
DefaultProjectData projectData =
(DefaultProjectData) project.addProjectView(view, true);
Msg.debug(this, "** added view: " + view);
assertNotNull(projectData);
DomainFile f = projectData.getFile("/Test");
assertNotNull(f);
// Open file and hold onto
dobj = f.getDomainObject(this, true, false, TaskMonitor.DUMMY);
Msg.debug(this, "** opened program: " + f);
assertFalse(projectData.isClosed());
assertFalse(projectData.isDisposed());
// remove the view while program open...
project.removeProjectView(view);
Msg.debug(this, "** removed view: " + view);
assertTrue(projectData.isClosed());
assertFalse(projectData.isDisposed());
Msg.debug(this, "** releasing program: " + f);
dobj.release(this);
dobj = null;
assertTrue(projectData.isClosed());
assertTrue(projectData.isDisposed());
}
finally {
if (dobj != null) {
dobj.release(this);
}
project.close();
}
}
} }
@@ -24,7 +24,7 @@ import java.util.Set;
import org.junit.*; import org.junit.*;
import generic.test.AbstractGTest; import generic.test.AbstractGTest;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
@@ -57,8 +57,8 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
projectLocator = new ProjectLocator(TEMP, "Test"); projectLocator = new ProjectLocator(TEMP, "Test");
project = TestProjectManager.get().createProject(projectLocator, null, true); project = TestProjectManager.get().createProject(projectLocator, null, true);
dataDir = dataDir =
new File(projectLocator.getProjectDir(), ProjectFileManager.INDEXED_DATA_FOLDER_NAME); new File(projectLocator.getProjectDir(), DefaultProjectData.INDEXED_DATA_FOLDER_NAME);
userDir = new File(projectLocator.getProjectDir(), ProjectFileManager.USER_FOLDER_NAME); userDir = new File(projectLocator.getProjectDir(), DefaultProjectData.USER_FOLDER_NAME);
ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY); ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY);
df = project.getProjectData() df = project.getProjectData()
@@ -81,8 +81,8 @@ public class FakeSharedProject {
// Note: this how we share multiple projects // Note: this how we share multiple projects
void setVersionedFileSystem(LocalFileSystem fs) { void setVersionedFileSystem(LocalFileSystem fs) {
ProjectFileManager fm = getProjectFileManager(); DefaultProjectData pd = getProjectData();
invokeInstanceMethod("setVersionedFileSystem", fm, argTypes(FileSystem.class), args(fs)); invokeInstanceMethod("setVersionedFileSystem", pd, argTypes(FileSystem.class), args(fs));
} }
/** /**
@@ -94,12 +94,12 @@ public class FakeSharedProject {
} }
/** /**
* Gets the project file manager * Gets the project data instance
* *
* @return the project file manager * @return the project data instance
*/ */
public ProjectFileManager getProjectFileManager() { public DefaultProjectData getProjectData() {
return (ProjectFileManager) gProject.getProjectData(); return (DefaultProjectData) gProject.getProjectData();
} }
/** /**
@@ -108,8 +108,8 @@ public class FakeSharedProject {
* @return the root folder of this project * @return the root folder of this project
*/ */
public RootGhidraFolder getRootFolder() { public RootGhidraFolder getRootFolder() {
ProjectFileManager pfm = getProjectFileManager(); DefaultProjectData pd = getProjectData();
return (RootGhidraFolder) pfm.getRootFolder(); return (RootGhidraFolder) pd.getRootFolder();
} }
/** /**
@@ -181,6 +181,7 @@ public class FakeSharedProject {
* <li>calling {@link #addDomainFile(String)}</li> * <li>calling {@link #addDomainFile(String)}</li>
* <li>Adding a versioned file to another project that shares the same repo with this project</li> * <li>Adding a versioned file to another project that shares the same repo with this project</li>
* </ul> * </ul>
* @param parentPath the parent folder path
* @param filename the filename * @param filename the filename
* @return the file * @return the file
*/ */
@@ -194,8 +195,7 @@ public class FakeSharedProject {
* Creates a folder by the given name in the given parent folder, creating the parent * Creates a folder by the given name in the given parent folder, creating the parent
* folder if needed * folder if needed
* *
* @param parentPath the parent folder path * @param path the full path of the folder to create
* @param name the name of the folder to create
* @return the created folder * @return the created folder
* @throws Exception if there are any exceptions creating the folder * @throws Exception if there are any exceptions creating the folder
*/ */
@@ -367,7 +367,7 @@ public class FakeSharedProject {
* @see FakeRepository#dispose() * @see FakeRepository#dispose()
*/ */
public void dispose() { public void dispose() {
ProjectLocator projectLocator = getProjectFileManager().getProjectLocator(); ProjectLocator projectLocator = getProjectData().getProjectLocator();
programManager.disposeOpenPrograms(); programManager.disposeOpenPrograms();
gProject.close(); gProject.close();
FileUtilities.deleteDir(projectLocator.getProjectDir()); FileUtilities.deleteDir(projectLocator.getProjectDir());
@@ -388,7 +388,7 @@ public class FakeSharedProject {
} }
ProjectLocator pl = df.getProjectLocator(); ProjectLocator pl = df.getProjectLocator();
ProjectLocator mypl = getProjectFileManager().getProjectLocator(); ProjectLocator mypl = getProjectData().getProjectLocator();
if (!pl.equals(mypl)) { if (!pl.equals(mypl)) {
throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " + throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " +
mypl.getName() + "\nYou must call addDomainFile(filename)."); mypl.getName() + "\nYou must call addDomainFile(filename).");
@@ -397,9 +397,8 @@ public class FakeSharedProject {
private void waitForFileSystemEvents() { private void waitForFileSystemEvents() {
LocalFileSystem versionedFileSystem = getVersionedFileSystem(); LocalFileSystem versionedFileSystem = getVersionedFileSystem();
FileSystemEventManager eventManager = FileSystemEventManager eventManager = (FileSystemEventManager) TestUtils
(FileSystemEventManager) TestUtils.getInstanceField("eventManager", .getInstanceField("eventManager", versionedFileSystem);
versionedFileSystem);
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS); eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
} }
@@ -444,16 +443,16 @@ public class FakeSharedProject {
} }
LocalFileSystem getVersionedFileSystem() { LocalFileSystem getVersionedFileSystem() {
ProjectFileManager fileManager = getProjectFileManager(); DefaultProjectData projectData = getProjectData();
LocalFileSystem fs = LocalFileSystem fs =
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", fileManager); (LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", projectData);
return fs; return fs;
} }
void refresh() { void refresh() {
ProjectFileManager fileManager = getProjectFileManager(); DefaultProjectData projectData = getProjectData();
try { try {
fileManager.refresh(true); projectData.refresh(true);
} }
catch (IOException e) { catch (IOException e) {
// shouldn't happen // shouldn't happen
@@ -33,215 +33,219 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*; import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
public class MergeTwoProgramsScript extends GhidraScript { public class MergeTwoProgramsScript extends GhidraScript {
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
if ( currentProgram == null ) { if (currentProgram == null) {
printerr( "Please open a program first!" ); printerr("Please open a program first!");
return; return;
} }
Program otherProgram = askProgram( "Select program from which to merge: " ); Program otherProgram = askProgram("Select program from which to merge: ");
if ( otherProgram == null ) { if (otherProgram == null) {
printerr( "Please select the other program first!" ); printerr("Please select the other program first!");
return; return;
} }
if ( !currentProgram.getLanguage().equals( otherProgram.getLanguage() ) ) { try {
printerr( "Incompatible program languages!" );
return; if (!currentProgram.getLanguage().equals(otherProgram.getLanguage())) {
printerr("Incompatible program languages!");
return;
}
if (currentProgram.getMemory().intersects(otherProgram.getMemory())) {
printerr("Memory map of current program must be disjoint from other program!");
return;
}
openProgram(currentProgram);
mergeMemory(currentProgram, otherProgram);
mergeSymbols(currentProgram, otherProgram);
mergeBookmarks(currentProgram, otherProgram);
mergeComments(currentProgram, otherProgram);
mergeData(currentProgram, otherProgram);
mergeInstructions(currentProgram, otherProgram);
mergeEquates(currentProgram, otherProgram);
mergeReferences(currentProgram, otherProgram);
} }
finally {
if ( currentProgram.getMemory().intersects( otherProgram.getMemory() ) ) { otherProgram.release(this);
printerr( "Memory map of current program must be disjoint from other program!" );
return;
} }
openProgram( currentProgram );
mergeMemory ( currentProgram, otherProgram );
mergeSymbols ( currentProgram, otherProgram );
mergeBookmarks ( currentProgram, otherProgram );
mergeComments ( currentProgram, otherProgram );
mergeData ( currentProgram, otherProgram );
mergeInstructions( currentProgram, otherProgram );
mergeEquates ( currentProgram, otherProgram );
mergeReferences ( currentProgram, otherProgram );
} }
private void mergeReferences( Program currProgram, Program otherProgram ) { private void mergeReferences(Program currProgram, Program otherProgram) {
monitor.setMessage( "Merging references..." ); monitor.setMessage("Merging references...");
ReferenceManager currentReferenceManager = currProgram.getReferenceManager(); ReferenceManager currentReferenceManager = currProgram.getReferenceManager();
ReferenceManager otherReferenceManager = otherProgram.getReferenceManager(); ReferenceManager otherReferenceManager = otherProgram.getReferenceManager();
ReferenceIterator otherReferenceIterator = otherReferenceManager.getReferenceIterator( otherProgram.getMinAddress() ); ReferenceIterator otherReferenceIterator =
while ( otherReferenceIterator.hasNext() ) { otherReferenceManager.getReferenceIterator(otherProgram.getMinAddress());
if ( monitor.isCancelled() ) { while (otherReferenceIterator.hasNext()) {
if (monitor.isCancelled()) {
break; break;
} }
Reference otherReference = otherReferenceIterator.next(); Reference otherReference = otherReferenceIterator.next();
if ( otherReference.isStackReference() ) { if (otherReference.isStackReference()) {
continue; continue;
} }
currentReferenceManager.addReference( otherReference ); currentReferenceManager.addReference(otherReference);
} }
} }
private void mergeInstructions( Program currProgram, Program otherProgram ) { private void mergeInstructions(Program currProgram, Program otherProgram) {
monitor.setMessage( "Merging instructions..." ); monitor.setMessage("Merging instructions...");
Listing currentListing = currProgram.getListing(); Listing currentListing = currProgram.getListing();
Listing otherListing = otherProgram.getListing(); Listing otherListing = otherProgram.getListing();
InstructionIterator otherInstructions = otherListing.getInstructions( true ); InstructionIterator otherInstructions = otherListing.getInstructions(true);
while ( otherInstructions.hasNext() ) { while (otherInstructions.hasNext()) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
Instruction otherInstruction = otherInstructions.next(); Instruction otherInstruction = otherInstructions.next();
if ( currentListing.isUndefined( otherInstruction.getMinAddress(), otherInstruction.getMaxAddress() ) ) { if (currentListing.isUndefined(otherInstruction.getMinAddress(),
disassemble( otherInstruction.getMinAddress() ); otherInstruction.getMaxAddress())) {
disassemble(otherInstruction.getMinAddress());
} }
} }
} }
private void mergeEquates( Program currProgram, Program otherProgram ) throws Exception { private void mergeEquates(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage( "Merging equates..." ); monitor.setMessage("Merging equates...");
EquateTable currentEquateTable = currProgram.getEquateTable(); EquateTable currentEquateTable = currProgram.getEquateTable();
EquateTable otherEquateTable = otherProgram.getEquateTable(); EquateTable otherEquateTable = otherProgram.getEquateTable();
Iterator<Equate> otherEquates = otherEquateTable.getEquates(); Iterator<Equate> otherEquates = otherEquateTable.getEquates();
while ( otherEquates.hasNext() ) { while (otherEquates.hasNext()) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
Equate otherEquate = otherEquates.next(); Equate otherEquate = otherEquates.next();
Equate currentEquate = currentEquateTable.createEquate( otherEquate.getName(), otherEquate.getValue() ); Equate currentEquate =
EquateReference [] otherEquateReferences = otherEquate.getReferences(); currentEquateTable.createEquate(otherEquate.getName(), otherEquate.getValue());
for ( EquateReference otherEquateReference : otherEquateReferences ) { EquateReference[] otherEquateReferences = otherEquate.getReferences();
if ( monitor.isCancelled() ) { for (EquateReference otherEquateReference : otherEquateReferences) {
if (monitor.isCancelled()) {
break; break;
} }
currentEquate.addReference( otherEquateReference.getAddress(), otherEquateReference.getOpIndex() ); currentEquate.addReference(otherEquateReference.getAddress(),
otherEquateReference.getOpIndex());
} }
} }
} }
private void mergeData( Program currProgram, Program otherProgram ) throws Exception { private void mergeData(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage( "Merging data..." ); monitor.setMessage("Merging data...");
Listing currentListing = currProgram.getListing(); Listing currentListing = currProgram.getListing();
Listing otherListing = otherProgram.getListing(); Listing otherListing = otherProgram.getListing();
DataIterator otherDataIterator = otherListing.getDefinedData( true ); DataIterator otherDataIterator = otherListing.getDefinedData(true);
while ( otherDataIterator.hasNext() ) { while (otherDataIterator.hasNext()) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
Data otherData = otherDataIterator.next(); Data otherData = otherDataIterator.next();
if ( currentListing.isUndefined( otherData.getMinAddress(), otherData.getMaxAddress() ) ) { if (currentListing.isUndefined(otherData.getMinAddress(), otherData.getMaxAddress())) {
currentListing.createData( otherData.getMinAddress(), otherData.getDataType() ); currentListing.createData(otherData.getMinAddress(), otherData.getDataType());
} }
} }
} }
private void mergeComments( Program currProgram, Program otherProgram ) throws Exception { private void mergeComments(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage( "Merging comments..." ); monitor.setMessage("Merging comments...");
int [] commentTypes = { int[] commentTypes = { CodeUnit.EOL_COMMENT, CodeUnit.PRE_COMMENT, CodeUnit.POST_COMMENT,
CodeUnit.EOL_COMMENT, CodeUnit.PLATE_COMMENT, CodeUnit.REPEATABLE_COMMENT, };
CodeUnit.PRE_COMMENT,
CodeUnit.POST_COMMENT,
CodeUnit.PLATE_COMMENT,
CodeUnit.REPEATABLE_COMMENT,
};
Listing currentListing = currProgram.getListing(); Listing currentListing = currProgram.getListing();
Listing otherListing = otherProgram.getListing(); Listing otherListing = otherProgram.getListing();
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits( true ); CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits(true);
while ( otherCodeUnits.hasNext() ) { while (otherCodeUnits.hasNext()) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
CodeUnit otherCodeUnit = otherCodeUnits.next(); CodeUnit otherCodeUnit = otherCodeUnits.next();
for ( int commentType : commentTypes ) { for (int commentType : commentTypes) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
String otherComment = otherCodeUnit.getComment( commentType ); String otherComment = otherCodeUnit.getComment(commentType);
if ( otherComment != null ) { if (otherComment != null) {
currentListing.setComment( otherCodeUnit.getAddress(), commentType, otherComment ); currentListing.setComment(otherCodeUnit.getAddress(), commentType,
otherComment);
} }
} }
} }
} }
private void mergeBookmarks( Program currProgram, Program otherProgram ) { private void mergeBookmarks(Program currProgram, Program otherProgram) {
monitor.setMessage( "Merging bookmarks..." ); monitor.setMessage("Merging bookmarks...");
BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager(); BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager();
BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager(); BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager();
Iterator<Bookmark> otherBookmarks = otherBookmarkManager.getBookmarksIterator(); Iterator<Bookmark> otherBookmarks = otherBookmarkManager.getBookmarksIterator();
while ( otherBookmarks.hasNext() ) { while (otherBookmarks.hasNext()) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
Bookmark otherBookmark = otherBookmarks.next(); Bookmark otherBookmark = otherBookmarks.next();
currentBookmarkManager.setBookmark( otherBookmark.getAddress(), currentBookmarkManager.setBookmark(otherBookmark.getAddress(),
otherBookmark.getTypeString(), otherBookmark.getTypeString(), otherBookmark.getCategory(),
otherBookmark.getCategory(), otherBookmark.getComment());
otherBookmark.getComment() );
} }
} }
private void mergeSymbols( Program currProgram, Program otherProgram ) throws Exception { private void mergeSymbols(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage( "Merging symbols..." ); monitor.setMessage("Merging symbols...");
SymbolTable currentSymbolTable = currProgram.getSymbolTable(); SymbolTable currentSymbolTable = currProgram.getSymbolTable();
SymbolTable otherSymbolTable = otherProgram.getSymbolTable(); SymbolTable otherSymbolTable = otherProgram.getSymbolTable();
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols( false ); SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols(false);
while ( otherSymbols.hasNext() ) { while (otherSymbols.hasNext()) {
if ( monitor.isCancelled() ) { if (monitor.isCancelled()) {
break; break;
} }
Symbol otherSymbol = otherSymbols.next(); Symbol otherSymbol = otherSymbols.next();
if ( otherSymbol.isDynamic() ) { if (otherSymbol.isDynamic()) {
continue; continue;
} }
try { try {
Namespace otherNamespace = otherSymbol.getParentNamespace(); Namespace otherNamespace = otherSymbol.getParentNamespace();
Namespace currentNamespace = mirrorNamespace( currProgram, otherProgram, otherNamespace ); Namespace currentNamespace =
if ( otherSymbol.getSymbolType() == SymbolType.FUNCTION ) { mirrorNamespace(currProgram, otherProgram, otherNamespace);
Function otherFunction = otherProgram.getListing().getFunctionAt( otherSymbol.getAddress() ); if (otherSymbol.getSymbolType() == SymbolType.FUNCTION) {
currProgram.getListing().createFunction( otherSymbol.getName(), Function otherFunction =
currentNamespace, otherProgram.getListing().getFunctionAt(otherSymbol.getAddress());
otherFunction.getEntryPoint(), currProgram.getListing()
otherFunction.getBody(), .createFunction(otherSymbol.getName(), currentNamespace,
SourceType.USER_DEFINED ); otherFunction.getEntryPoint(), otherFunction.getBody(),
SourceType.USER_DEFINED);
} }
else { else {
currentSymbolTable.createLabel( otherSymbol.getAddress(), currentSymbolTable.createLabel(otherSymbol.getAddress(), otherSymbol.getName(),
otherSymbol.getName(), currentNamespace, SourceType.USER_DEFINED);
currentNamespace,
SourceType.USER_DEFINED );
} }
} }
catch ( Exception e ) { catch (Exception e) {
printerr( "Unable to create symbol: " + otherSymbol.getName() ); printerr("Unable to create symbol: " + otherSymbol.getName());
} }
} }
} }
private Namespace mirrorNamespace( Program currProgram, Program otherProgram, Namespace otherNamespace ) throws Exception { private Namespace mirrorNamespace(Program currProgram, Program otherProgram,
if ( otherNamespace == null ) { Namespace otherNamespace) throws Exception {
if (otherNamespace == null) {
return currProgram.getGlobalNamespace(); return currProgram.getGlobalNamespace();
} }
SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script! SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script!
try { try {
source = otherNamespace.getSymbol().getSource(); source = otherNamespace.getSymbol().getSource();
} }
catch ( Exception e ) { catch (Exception e) {
} }
return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null, return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null,
currProgram, source); currProgram, source);
} }
private void mergeMemory( Program currProgram, Program otherProgram ) throws Exception { private void mergeMemory(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage( "Merging memory..." ); monitor.setMessage("Merging memory...");
Memory otherMemory = otherProgram.getMemory(); Memory otherMemory = otherProgram.getMemory();
MemoryBlock[] otherBlocks = otherMemory.getBlocks(); MemoryBlock[] otherBlocks = otherMemory.getBlocks();
MessageLog log = new MessageLog(); MessageLog log = new MessageLog();
@@ -17,7 +17,6 @@
// data and and then save the session. // data and and then save the session.
//@category Examples.Version Tracking //@category Examples.Version Tracking
import java.util.Iterator;
import java.util.List; import java.util.List;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
@@ -32,6 +31,21 @@ import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskLauncher;
public class AutoVersionTrackingScript extends GhidraScript { public class AutoVersionTrackingScript extends GhidraScript {
private Program sourceProgram;
private Program destinationProgram;
@Override
public void cleanup(boolean success) {
if (sourceProgram != null && sourceProgram.isUsedBy(this)) {
sourceProgram.release(this);
}
if (destinationProgram != null && destinationProgram.isUsedBy(this)) {
destinationProgram.release(this);
}
super.cleanup(success);
}
@Override @Override
public void run() throws Exception { public void run() throws Exception {
@@ -39,9 +53,6 @@ public class AutoVersionTrackingScript extends GhidraScript {
askProjectFolder("Please choose a folder for your Version Tracking session."); askProjectFolder("Please choose a folder for your Version Tracking session.");
String name = askString("Please enter a Version Tracking session name", "Session Name"); String name = askString("Please enter a Version Tracking session name", "Session Name");
Program sourceProgram;
Program destinationProgram;
boolean isCurrentProgramSourceProg = askYesNo("Current Program Source Program?", boolean isCurrentProgramSourceProg = askYesNo("Current Program Source Program?",
"Is the current program your source program?"); "Is the current program your source program?");
@@ -54,6 +65,10 @@ public class AutoVersionTrackingScript extends GhidraScript {
sourceProgram = askProgram("Please select the source (existing annotated) program"); sourceProgram = askProgram("Please select the source (existing annotated) program");
} }
if (sourceProgram == null || destinationProgram == null) {
return;
}
// 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);
@@ -81,9 +96,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
public static <T extends Plugin> T getPlugin(PluginTool tool, Class<T> c) { public static <T extends Plugin> T getPlugin(PluginTool tool, Class<T> c) {
List<Plugin> list = tool.getManagedPlugins(); List<Plugin> list = tool.getManagedPlugins();
Iterator<Plugin> it = list.iterator(); for (Plugin p : list) {
while (it.hasNext()) {
Plugin p = it.next();
if (p.getClass() == c) { if (p.getClass() == c) {
return c.cast(p); return c.cast(p);
} }
@@ -17,6 +17,9 @@
// data and and then save the session. // data and and then save the session.
//@category Examples.Version Tracking //@category Examples.Version Tracking
import java.util.Collection;
import java.util.List;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.feature.vt.api.correlator.program.*; import ghidra.feature.vt.api.correlator.program.*;
import ghidra.feature.vt.api.db.VTSessionDB; import ghidra.feature.vt.api.db.VTSessionDB;
@@ -31,17 +34,35 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import java.util.Collection;
import java.util.List;
public class CreateAppliedExactMatchingSessionScript extends GhidraScript { public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
private Program sourceProgram;
private Program destinationProgram;
@Override
public void cleanup(boolean success) {
if (sourceProgram != null) {
sourceProgram.release(this);
}
if (destinationProgram != null) {
destinationProgram.release(this);
}
super.cleanup(success);
}
@Override @Override
public void run() throws Exception { public void run() throws Exception {
DomainFolder folder = DomainFolder folder =
askProjectFolder("Please choose a folder for the session domain object"); askProjectFolder("Please choose a folder for the session domain object");
String name = askString("Please enter a Version Tracking session name", "Session Name"); String name = askString("Please enter a Version Tracking session name", "Session Name");
Program sourceProgram = askProgram("Please select the source (existing annotated) program"); sourceProgram = askProgram("Please select the source (existing annotated) program");
Program destinationProgram = askProgram("Please select the destination (new) program"); if (sourceProgram == null) {
return;
}
destinationProgram = askProgram("Please select the destination (new) program");
if (destinationProgram == null) {
return;
}
VTSession session = VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this); VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
@@ -58,7 +79,8 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
// should we have convenience methods in VTCorrelator that don't // should we have convenience methods in VTCorrelator that don't
// take address sets, thus implying the entire address space should be used? // take address sets, thus implying the entire address space should be used?
AddressSetView sourceAddressSet = sourceProgram.getMemory().getLoadedAndInitializedAddressSet(); AddressSetView sourceAddressSet =
sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
AddressSetView destinationAddressSet = AddressSetView destinationAddressSet =
destinationProgram.getMemory().getLoadedAndInitializedAddressSet(); destinationProgram.getMemory().getLoadedAndInitializedAddressSet();
@@ -91,17 +113,16 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram, private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram,
VTSession session, PluginTool serviceProvider, VTAssociationManager manager, VTSession session, PluginTool serviceProvider, VTAssociationManager manager,
AddressSetView sourceAddressSet, AddressSetView destinationAddressSet, AddressSetView sourceAddressSet, AddressSetView destinationAddressSet,
VTProgramCorrelatorFactory factory) throws CancelledException, VTProgramCorrelatorFactory factory)
VTAssociationStatusException { throws CancelledException, VTAssociationStatusException {
AddressSetView restrictedSourceAddresses = AddressSetView restrictedSourceAddresses =
excludeAcceptedMatches(session, sourceAddressSet, true); excludeAcceptedMatches(session, sourceAddressSet, true);
AddressSetView restrictedDestinationAddresses = AddressSetView restrictedDestinationAddresses =
excludeAcceptedMatches(session, destinationAddressSet, false); excludeAcceptedMatches(session, destinationAddressSet, false);
VTOptions options = factory.createDefaultOptions(); VTOptions options = factory.createDefaultOptions();
VTProgramCorrelator correlator = VTProgramCorrelator correlator = factory.createCorrelator(serviceProvider, sourceProgram,
factory.createCorrelator(serviceProvider, sourceProgram, restrictedSourceAddresses, restrictedSourceAddresses, destinationProgram, restrictedDestinationAddresses, options);
destinationProgram, restrictedDestinationAddresses, options);
VTMatchSet results = correlator.correlate(session, monitor); VTMatchSet results = correlator.correlate(session, monitor);
applyMatches(manager, results.getMatches()); applyMatches(manager, results.getMatches());
@@ -29,26 +29,47 @@ import ghidra.util.Msg;
public class FindChangedFunctionsScript extends GhidraVersionTrackingScript { public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
private Program p1;
private Program p2;
@Override
public void cleanup(boolean success) {
if (p1 != null) {
p1.release(this);
}
if (p2 != null) {
p2.release(this);
}
super.cleanup(success);
}
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
Project project = state.getProject(); Project project = state.getProject();
if (project == null) { if (project == null) {
throw new RuntimeException("No project open"); throw new RuntimeException("No project open");
} }
// Prompt the user to load the two programs that will be analyzed. // Prompt the user to load the two programs that will be analyzed.
// This will only allow you to select programs from the currently-open // This will only allow you to select programs from the currently-open
// project in Ghidra, so import them if you haven't already. // project in Ghidra, so import them if you haven't already.
Program p1 = askProgram("Program1_Version1"); p1 = askProgram("Program1_Version1");
Program p2 = askProgram("Program1_Version2"); if (p1 == null) {
return;
}
p2 = askProgram("Program1_Version2");
if (p2 == null) {
return;
}
// Make sure the selected programs are not open and locked by Ghidra. If so, // Make sure the selected programs are not open and locked by Ghidra. If so,
// warn the user. // warn the user.
if (areProgramsLocked(p1, p2)) { if (areProgramsLocked(p1, p2)) {
Msg.showError(this, null, "Program is locked!", "One of the programs you selected is locked by Ghidra. Please correct and try again."); Msg.showError(this, null, "Program is locked!",
"One of the programs you selected is locked by Ghidra. Please correct and try again.");
return; return;
} }
// Create a new VT session // Create a new VT session
createVersionTrackingSession("new session", p1, p2); createVersionTrackingSession("new session", p1, p2);
@@ -67,7 +88,7 @@ public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
println("Did not find exact match for: " + functionName); println("Did not find exact match for: " + functionName);
} }
} }
/** /**
* Returns true if one of the programs is locked. * Returns true if one of the programs is locked.
* <p> * <p>
@@ -82,7 +82,7 @@ public class AutoVersionTrackingTask extends Task {
private static int NUM_CORRELATORS = 8; private static int NUM_CORRELATORS = 8;
/** /**
* Constructor for AutoVersionTrackingCommand * Constructor for a modal/blocking AutoVersionTrackingTask
* *
* @param controller The Version Tracking controller for this session containing option and * @param controller The Version Tracking controller for this session containing option and
* tool information needed for this command. * tool information needed for this command.
@@ -483,8 +483,6 @@ public class AutoVersionTrackingTask extends Task {
continue; continue;
} }
// remove any matches that have identical source functions - if more than one // remove any matches that have identical source functions - if more than one
// with exactly the same instructions and operands then cannot determine a unique match // with exactly the same instructions and operands then cannot determine a unique match
Set<Address> sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor); Set<Address> sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor);
@@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
return; return;
} }
String path = "/"; String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID); String name = DefaultProjectData.getUserDataFilename(associatedFileID);
BufferFile bf = null; BufferFile bf = null;
boolean success = false; boolean success = false;
try { try {
@@ -109,7 +109,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem) public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
throws IOException { throws IOException {
String path = "/"; String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID()); String name = DefaultProjectData.getUserDataFilename(associatedItem.getFileID());
FolderItem item = userFilesystem.getItem(path, name); FolderItem item = userFilesystem.getItem(path, name);
if (item != null) { if (item != null) {
item.delete(-1, null); item.delete(-1, null);
@@ -130,7 +130,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
String associatedContentType, FileSystem userfs, TaskMonitor monitor) String associatedContentType, FileSystem userfs, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
String path = "/"; String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID); String name = DefaultProjectData.getUserDataFilename(associatedFileID);
FolderItem item = userfs.getItem(path, name); FolderItem item = userfs.getItem(path, name);
if (item == null || !(item instanceof DatabaseItem) || if (item == null || !(item instanceof DatabaseItem) ||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) { !getUserDataContentType(associatedContentType).equals(item.getContentType())) {
@@ -16,12 +16,14 @@
package ghidra.framework.data; package ghidra.framework.data;
import java.io.*; import java.io.*;
import java.net.URL;
import java.util.*; import java.util.*;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import generic.timer.GhidraSwinglessTimer; import generic.timer.GhidraSwinglessTimer;
import ghidra.framework.client.*; import ghidra.framework.client.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.User; import ghidra.framework.remote.User;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystem;
@@ -37,9 +39,16 @@ import utilities.util.FileUtilities;
/** /**
* Helper class to manage files within a project. * Helper class to manage files within a project.
*/ */
public class ProjectFileManager implements ProjectData { public class DefaultProjectData implements ProjectData {
/**Name of folder that stores user's data*/ /**
* {@code fileTrackingMap} is used to identify DefaultProjectData instances which are
* tracking specific DomainObjectAdapter instances which are open.
*/
private static Map<DomainObjectAdapter, DefaultProjectData> fileTrackingMap =
Collections.synchronizedMap(new IdentityHashMap<>());
// Names of folders that stores project data
public static final String MANGLED_DATA_FOLDER_NAME = "data"; public static final String MANGLED_DATA_FOLDER_NAME = "data";
public static final String INDEXED_DATA_FOLDER_NAME = "idata"; public static final String INDEXED_DATA_FOLDER_NAME = "idata";
public static final String USER_FOLDER_NAME = "user"; public static final String USER_FOLDER_NAME = "user";
@@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData {
private RootGhidraFolderData rootFolderData; private RootGhidraFolderData rootFolderData;
private Map<String, DomainObjectAdapter> openDomainObjects = private Map<String, DomainObjectAdapter> openDomainObjects = new HashMap<>();
new HashMap<>();
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter(); private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
private ProjectLock projectLock; private ProjectLock projectLock;
private String owner; private String owner;
private int inUseCount = 0; // open file count plus active merge sessions
private boolean closed = false;
private boolean disposed = false;
/** /**
* Constructor for existing projects. * Constructor for existing projects.
* @param localStorageLocator the location of the project * @param localStorageLocator the location of the project
@@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData {
* write lock (i.e., project in-use) * write lock (i.e., project in-use)
* @throws FileNotFoundException if project directory not found * @throws FileNotFoundException if project directory not found
*/ */
public ProjectFileManager(ProjectLocator localStorageLocator, boolean isInWritableProject, public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject,
boolean resetOwner) throws NotOwnerException, IOException, LockException { boolean resetOwner) throws NotOwnerException, IOException, LockException {
this.localStorageLocator = localStorageLocator; this.localStorageLocator = localStorageLocator;
@@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData {
* @throws LockException if {@code isInWritableProject} is true and unable to establish project * @throws LockException if {@code isInWritableProject} is true and unable to establish project
* lock (i.e., project in-use) * lock (i.e., project in-use)
*/ */
public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository, public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
boolean isInWritableProject) throws IOException, LockException { boolean isInWritableProject) throws IOException, LockException {
this.localStorageLocator = localStorageLocator; this.localStorageLocator = localStorageLocator;
this.repository = repository; this.repository = repository;
@@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData {
* @param versionedFileSystem an existing versioned file-system * @param versionedFileSystem an existing versioned file-system
* @throws IOException if an IO error occurs * @throws IOException if an IO error occurs
*/ */
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem) DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
throws IOException { throws IOException {
this.localStorageLocator = new ProjectLocator(null, "Test"); this.localStorageLocator = new ProjectLocator(null, "Test");
owner = SystemUtilities.getUserName(); owner = SystemUtilities.getUserName();
@@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData {
/** /**
* Returns the owner of the project that is associated with this * Returns the owner of the project that is associated with this
* ProjectFileManager. A value of null indicates an old multiuser * DefaultProjectData. A value of null indicates an old multiuser
* project. * project.
* @return the owner of the project * @return the owner of the project
*/ */
@@ -731,9 +743,8 @@ public class ProjectFileManager implements ProjectData {
@Override @Override
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force, public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
TaskMonitor monitor) TaskMonitor monitor) throws IOException, CancelledException {
throws IOException, CancelledException {
newRepository.connect(); newRepository.connect();
if (!newRepository.isConnected()) { if (!newRepository.isConnected()) {
throw new IOException("new respository not connected"); throw new IOException("new respository not connected");
@@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData {
long checkoutId = item.getCheckoutId(); long checkoutId = item.getCheckoutId();
int checkoutVersion = item.getCheckoutVersion(); int checkoutVersion = item.getCheckoutVersion();
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout( ItemCheckoutStatus otherCheckoutStatus =
df.getParent().getPathname(), df.getName(), checkoutId); newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId);
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) { if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
return true; return true;
@@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData {
* @throws IOException if IO error occurs * @throws IOException if IO error occurs
* @throws CancelledException if task cancelled * @throws CancelledException if task cancelled
*/ */
@Override
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList, public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
RepositoryAdapter newRepository, TaskMonitor monitor) RepositoryAdapter newRepository, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
@@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData {
* @throws IOException if IO error occurs * @throws IOException if IO error occurs
* @throws CancelledException if task cancelled * @throws CancelledException if task cancelled
*/ */
@Override
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor) public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
List<DomainFile> list = new ArrayList<>(); List<DomainFile> list = new ArrayList<>();
@@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData {
} }
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList, private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
TaskMonitor monitor) TaskMonitor monitor) throws IOException, CancelledException {
throws IOException, CancelledException {
for (String name : fileSystem.getItemNames(folderPath)) { for (String name : fileSystem.getItemNames(folderPath)) {
monitor.checkCancelled(); monitor.checkCancelled();
@@ -902,6 +914,30 @@ public class ProjectFileManager implements ProjectData {
} }
} }
@Override
public URL getSharedProjectURL() {
URL projectURL = localStorageLocator.getURL();
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
if (repository == null) {
return null;
}
// NOTE: only supports ghidra protocol without extension protocol.
// Assumes any extension protocol use would be reflected in ProjectLocator URL.
ServerInfo serverInfo = repository.getServerInfo();
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName());
}
return projectURL;
}
@Override
public URL getLocalProjectURL() {
if (!localStorageLocator.isTransient()) {
return localStorageLocator.getURL();
}
return null;
}
/** /**
* Returns the standard user data filename associated with the specified file ID. * Returns the standard user data filename associated with the specified file ID.
* @param associatedFileID the file id * @param associatedFileID the file id
@@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData {
} }
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> { userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
synchronized (ProjectFileManager.this) { synchronized (DefaultProjectData.this) {
startReconcileUserDataFiles(); startReconcileUserDataFiles();
} }
}); });
@@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData {
return projectDir; return projectDir;
} }
public synchronized boolean isClosed() {
return closed;
}
public synchronized boolean isDisposed() {
return disposed;
}
@Override @Override
public void close() { public void close() {
synchronized (this) {
if (!closed) {
Msg.debug(this, "Closing ProjectData: " + projectDir);
closed = true;
}
if (inUseCount != 0) {
return; // delay dispose
}
}
dispose(); dispose();
} }
public void dispose() { private synchronized void incrementInUseCount() {
++inUseCount;
}
private void decrementInUseCount() {
synchronized (this) {
if (inUseCount <= 0) {
Msg.error(this, "DefaultProjectData in-use tracking is out-of-sync: " + projectDir);
}
if (--inUseCount > 0 || !closed) {
return;
}
}
dispose();
}
/**
* Immediately dispose this project data store instance. If this project has an associated
* {@link RepositoryAdapter} it will be disconnected as well. This method should generally not
* be used directly when there may be open {@link DomainObject} instances which may rely
* on an associated server connection. The {@link #clone()} method should be used when
* open {@link DomainObject} instances may exist and should be allowed to persist until
* they are closed.
*/
protected void dispose() {
synchronized (this) { synchronized (this) {
if (disposed) {
return;
}
Msg.debug(this, "Disposing ProjectData: " + projectDir);
closed = true;
disposed = true;
if (userDataReconcileTimer != null) { if (userDataReconcileTimer != null) {
userDataReconcileTimer.stop(); userDataReconcileTimer.stop();
} }
@@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData {
/** /**
* Returns the open domain object (opened for update) for the specified path. * Returns the open domain object (opened for update) for the specified path.
* @param pathname the path name * @param pathname the path name
* @return the domain object * @return the domain object or null if not open
*/ */
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) { synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
return openDomainObjects.get(pathname); return openDomainObjects.get(pathname);
@@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData {
public TaskMonitor getProjectDisposalMonitor() { public TaskMonitor getProjectDisposalMonitor() {
return projectDisposalMonitor; return projectDisposalMonitor;
} }
/**
* Signals the start of a complex merge operation.
* The {@link #mergeEnded()} must be invoked after this method invocation when the
* merge operation has completed.
*/
void mergeStarted() {
incrementInUseCount();
}
/**
* Signals the completion of a complex merge operation (see {@link #mergeStarted()}).
*/
void mergeEnded() {
decrementInUseCount();
}
/**
* Signals that a <b>non-link</b> file has been opened as the specified
* {@link DomainObjectAdapter doa} from this project data store and should be
* tracked. This will delay disposal of this object until the specified domain object is
* either closed or saved to a different project store (i.e., hand-off operation).
* It is important that this method not be invoked when opening a link-file
* since it is the referenced file being opened that must be tracked and not the
* opening of the link-file itself.
* @param doa domain object
*/
void trackDomainFileInUse(DomainObjectAdapter doa) {
DefaultProjectData projectData = fileTrackingMap.put(doa, this);
if (projectData == this) {
return; // no change in associated project
}
if (projectData != null) {
projectData.decrementInUseCount();
}
else {
doa.addCloseListener(dobj -> domainObjectClosed(dobj));
}
incrementInUseCount();
}
private static void domainObjectClosed(DomainObject dobj) {
DefaultProjectData projectData = fileTrackingMap.remove(dobj);
if (projectData != null) {
projectData.decrementInUseCount();
}
}
} }
@@ -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.
@@ -29,10 +28,10 @@ import java.util.HashMap;
*/ */
class DomainFileIndex implements DomainFolderChangeListener { class DomainFileIndex implements DomainFolderChangeListener {
private ProjectFileManager projectData; private DefaultProjectData projectData;
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>(); private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
DomainFileIndex(ProjectFileManager projectData) { DomainFileIndex(DefaultProjectData projectData) {
this.projectData = projectData; this.projectData = projectData;
} }
@@ -23,14 +23,11 @@ import java.util.*;
import javax.swing.Icon; import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.*; import ghidra.framework.client.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.RepositoryItem; import ghidra.framework.remote.RepositoryItem;
import ghidra.framework.store.ItemCheckoutStatus; import ghidra.framework.store.*;
import ghidra.framework.store.Version;
import ghidra.framework.store.db.PackedDatabase; import ghidra.framework.store.db.PackedDatabase;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.ReadOnlyException; import ghidra.util.ReadOnlyException;
@@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile {
private URL getSharedFileURL(URL sharedProjectURL, String ref) { private URL getSharedFileURL(URL sharedProjectURL, String ref) {
try { try {
String spec = getPathname().substring(1); // remove leading '/' // Direct URL construction done so that ghidra protocol extension may be supported
if (!StringUtils.isEmpty(ref)) { String urlStr = sharedProjectURL.toExternalForm();
spec += "#" + ref; if (urlStr.endsWith(FileSystem.SEPARATOR)) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
} }
return new URL(sharedProjectURL, spec); urlStr += getPathname();
return new URL(urlStr);
} }
catch (MalformedURLException e) { catch (MalformedURLException e) {
// ignore // ignore
@@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile {
if (properties == null) { if (properties == null) {
return null; return null;
} }
String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME); String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME);
String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME); String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME);
if (serverName == null || repoName == null) { if (serverName == null || repoName == null) {
return null; return null;
} }
int port = Integer.parseInt(properties.getProperty(ProjectFileManager.PORT_NUMBER, "0")); int port = Integer.parseInt(properties.getProperty(DefaultProjectData.PORT_NUMBER, "0"));
if (!ClientUtil.isConnected(serverName, port)) { if (!ClientUtil.isConnected(serverName, port)) {
return null; // avoid initiating a server connection. return null; // avoid initiating a server connection.
@@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile {
return getSharedFileURL(projectURL, ref); return getSharedFileURL(projectURL, ref);
} }
Properties properties = Properties properties =
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir()); DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
return getSharedFileURL(properties, ref); return getSharedFileURL(properties, ref);
} }
return null; return null;
@@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap = protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>(); new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
private volatile boolean eventsEnabled = true; private volatile boolean eventsEnabled = true;
private Set<DomainObjectClosedListener> closeListeners = private Set<DomainObjectClosedListener> closeListeners = new CopyOnWriteArraySet<>();
new CopyOnWriteArraySet<DomainObjectClosedListener>();
private ArrayList<Object> consumers; private ArrayList<Object> consumers;
protected Map<String, String> metadata = new LinkedHashMap<String, String>(); protected Map<String, String> metadata = new LinkedHashMap<String, String>();
@@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
private void notifyCloseListeners() { private void notifyCloseListeners() {
for (DomainObjectClosedListener listener : closeListeners) { for (DomainObjectClosedListener listener : closeListeners) {
listener.domainObjectClosed(); listener.domainObjectClosed(this);
} }
closeListeners.clear(); closeListeners.clear();
} }
@@ -33,7 +33,7 @@ public class GhidraFile implements DomainFile {
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces // FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
protected ProjectFileManager fileManager; protected DefaultProjectData projectData;
private LocalFileSystem fileSystem; private LocalFileSystem fileSystem;
private DomainFolderChangeListener listener; private DomainFolderChangeListener listener;
@@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this.fileManager = parent.getProjectFileManager(); this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem(); this.fileSystem = parent.getLocalFileSystem();
this.listener = parent.getChangeListener(); this.listener = parent.getChangeListener();
} }
public LocalFileSystem getUserFileSystem() { public LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem(); return projectData.getUserFileSystem();
} }
private GhidraFileData getFileData() throws FileNotFoundException, IOException { private GhidraFileData getFileData() throws FileNotFoundException, IOException {
@@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile {
void clearDomainObj() { void clearDomainObj() {
String path = getPathname(); String path = getPathname();
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path); DomainObjectAdapter doa = projectData.getOpenedDomainObject(path);
if (doa != null && fileManager.clearDomainObject(getPathname())) { if (doa != null && projectData.clearDomainObject(getPathname())) {
listener.domainFileObjectClosed(this, doa); listener.domainFileObjectClosed(this, doa);
} }
} }
@@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile {
@Override @Override
public ProjectLocator getProjectLocator() { public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator(); return projectData.getProjectLocator();
} }
@Override @Override
@@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile {
@Override @Override
public DomainObject getOpenedDomainObject(Object consumer) { public DomainObject getOpenedDomainObject(Object consumer) {
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname()); DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname());
if (domainObj != null) { if (domainObj != null) {
if (!domainObj.addConsumer(consumer)) { if (!domainObj.addConsumer(consumer)) {
fileManager.clearDomainObject(getPathname()); projectData.clearDomainObject(getPathname());
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName()); throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
} }
} }
@@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile {
@Override @Override
public void save(TaskMonitor monitor) throws IOException, CancelledException { public void save(TaskMonitor monitor) throws IOException, CancelledException {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) { if (dobj == null) {
throw new AssertException("Cannot save, domainObj not open"); throw new AssertException("Cannot save, domainObj not open");
} }
@@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile {
@Override @Override
public boolean canSave() { public boolean canSave() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) { if (dobj == null) {
return false; return false;
} }
@@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile {
@Override @Override
public ArrayList<?> getConsumers() { public ArrayList<?> getConsumers() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) { if (dobj == null) {
return new ArrayList<>(); return new ArrayList<>();
} }
@@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile {
@Override @Override
public boolean isChanged() { public boolean isChanged() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
return dobj != null && dobj.isChanged(); return dobj != null && dobj.isChanged();
} }
@Override @Override
public boolean isOpen() { public boolean isOpen() {
return fileManager.getOpenedDomainObject(getPathname()) != null; return projectData.getOpenedDomainObject(getPathname()) != null;
} }
@Override @Override
@@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile {
return false; return false;
} }
GhidraFile other = (GhidraFile) obj; GhidraFile other = (GhidraFile) obj;
if (fileManager != other.fileManager) { if (projectData != other.projectData) {
return false; return false;
} }
return getPathname().equals(other.getPathname()); return getPathname().equals(other.getPathname());
@@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile {
@Override @Override
public String toString() { public String toString() {
ProjectLocator projectLocator = parent.getProjectData().getProjectLocator(); ProjectLocator projectLocator = projectData.getProjectLocator();
if (projectLocator.isTransient()) { if (projectLocator.isTransient()) {
return fileManager.getProjectLocator().getName() + getPathname(); return projectLocator.getName() + getPathname();
} }
return fileManager.getProjectLocator().getName() + ":" + getPathname(); return projectLocator.getName() + ":" + getPathname();
} }
} }
File diff suppressed because it is too large Load Diff
@@ -20,7 +20,6 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystem;
@@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
public class GhidraFolder implements DomainFolder { public class GhidraFolder implements DomainFolder {
private ProjectFileManager fileManager; private DefaultProjectData projectData;
private LocalFileSystem fileSystem; private LocalFileSystem fileSystem;
private FileSystem versionedFileSystem; private FileSystem versionedFileSystem;
private DomainFolderChangeListener listener; private DomainFolderChangeListener listener;
@@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder {
private GhidraFolder parent; private GhidraFolder parent;
private String name; private String name;
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) { GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
this.fileManager = fileManager; this.projectData = projectData;
this.fileSystem = fileManager.getLocalFileSystem(); this.fileSystem = projectData.getLocalFileSystem();
this.versionedFileSystem = fileManager.getVersionedFileSystem(); this.versionedFileSystem = projectData.getVersionedFileSystem();
this.listener = listener; this.listener = listener;
this.name = FileSystem.SEPARATOR; this.name = FileSystem.SEPARATOR;
} }
@@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this.fileManager = parent.getProjectFileManager(); this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem(); this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem(); this.versionedFileSystem = parent.getVersionedFileSystem();
this.listener = parent.getChangeListener(); this.listener = parent.getChangeListener();
@@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder {
} }
LocalFileSystem getUserFileSystem() { LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem(); return projectData.getUserFileSystem();
} }
DomainFolderChangeListener getChangeListener() { DomainFolderChangeListener getChangeListener() {
return listener; return listener;
} }
ProjectFileManager getProjectFileManager() {
return fileManager;
}
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException { GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
GhidraFileData fileData = getFolderData().getFileData(fileName, false); GhidraFileData fileData = getFolderData().getFileData(fileName, false);
if (fileData == null) { if (fileData == null) {
@@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder {
GhidraFolderData getFolderData() throws FileNotFoundException { GhidraFolderData getFolderData() throws FileNotFoundException {
if (parent == null) { if (parent == null) {
return fileManager.getRootFolderData(); return projectData.getRootFolderData();
} }
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false); GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
if (folderData == null) { if (folderData == null) {
@@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder {
private GhidraFolderData createFolderData(String folderName) throws IOException { private GhidraFolderData createFolderData(String folderName) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
GhidraFolderData parentData = GhidraFolderData parentData =
parent == null ? fileManager.getRootFolderData() : createFolderData(); parent == null ? projectData.getRootFolderData() : createFolderData();
GhidraFolderData folderData = parentData.getFolderData(folderName, false); GhidraFolderData folderData = parentData.getFolderData(folderName, false);
if (folderData == null) { if (folderData == null) {
try { try {
@@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder {
} }
private GhidraFolderData createFolderData() throws IOException { private GhidraFolderData createFolderData() throws IOException {
GhidraFolderData rootFolderData = fileManager.getRootFolderData(); GhidraFolderData rootFolderData = projectData.getRootFolderData();
if (parent == null) { if (parent == null) {
return rootFolderData; return rootFolderData;
} }
@@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder {
@Override @Override
public ProjectLocator getProjectLocator() { public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator(); return projectData.getProjectLocator();
} }
@Override @Override
public ProjectFileManager getProjectData() { public DefaultProjectData getProjectData() {
return fileManager; return projectData;
} }
String getPathname(String childName) { String getPathname(String childName) {
@@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder {
@Override @Override
public URL getSharedProjectURL() { public URL getSharedProjectURL() {
ProjectLocator projectLocator = getProjectLocator(); URL projectURL = projectData.getSharedProjectURL();
URL projectURL = projectLocator.getURL(); if (projectURL == null) {
if (!GhidraURL.isServerRepositoryURL(projectURL)) { return null;
RepositoryAdapter repository = fileManager.getRepository();
if (repository == null) {
return null;
}
// NOTE: only supports ghidra protocol without extension protocol.
// Assumes any extension protocol use would be reflected in projectLocator URL.
ServerInfo serverInfo = repository.getServerInfo();
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName());
} }
try { try {
// Direct URL construction done so that ghidra protocol extension may be supported // Direct URL construction done so that ghidra protocol extension may be supported
@@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder {
@Override @Override
public URL getLocalProjectURL() { public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator(); ProjectLocator projectLocator = projectData.getProjectLocator();
if (!projectLocator.isTransient()) { if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null); return GhidraURL.makeURL(projectLocator, getPathname(), null);
} }
@@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder {
@Override @Override
public boolean isInWritableProject() { public boolean isInWritableProject() {
return !getProjectData().getLocalFileSystem().isReadOnly(); return !fileSystem.isReadOnly();
} }
@Override @Override
@@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder {
} }
} }
catch (IOException e) { catch (IOException e) {
Msg.error(this, "file error for " + parent.getPathname(fileName), e); Msg.error(this, "file error for " + getPathname(fileName), e);
} }
return null; return null;
} }
@@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder {
return false; return false;
} }
GhidraFolder other = (GhidraFolder) obj; GhidraFolder other = (GhidraFolder) obj;
if (fileManager != other.fileManager) { if (projectData != other.projectData) {
return false; return false;
} }
return getPathname().equals(other.getPathname()); return getPathname().equals(other.getPathname());
@@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder {
@Override @Override
public String toString() { public String toString() {
ProjectLocator projectLocator = fileManager.getProjectLocator(); ProjectLocator projectLocator = projectData.getProjectLocator();
if (projectLocator.isTransient()) { if (projectLocator.isTransient()) {
return fileManager.getProjectLocator().getName() + getPathname(); return projectData.getProjectLocator().getName() + getPathname();
} }
return fileManager.getProjectLocator().getName() + ":" + getPathname(); return projectData.getProjectLocator().getName() + ":" + getPathname();
} }
} }
File diff suppressed because it is too large Load Diff
@@ -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.
@@ -20,8 +19,8 @@ import ghidra.framework.model.DomainFolderChangeListener;
public class RootGhidraFolder extends GhidraFolder { public class RootGhidraFolder extends GhidraFolder {
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) { RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
super(fileManager, listener); super(projectData, listener);
} }
} }
@@ -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.
@@ -21,18 +20,18 @@ import ghidra.framework.store.FileSystem;
public class RootGhidraFolderData extends GhidraFolderData { public class RootGhidraFolderData extends GhidraFolderData {
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) { RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
super(fileManager, listener); super(projectData, listener);
} }
@Override @Override
GhidraFolder getDomainFolder() { GhidraFolder getDomainFolder() {
return new RootGhidraFolder(getProjectFileManager(), getChangeListener()); return new RootGhidraFolder(getProjectData(), getChangeListener());
} }
/** /**
* Provided for testing use only * Provided for testing use only
* @param fs * @param fs versioned file system
*/ */
void setVersionedFileSystem(FileSystem fs) { void setVersionedFileSystem(FileSystem fs) {
versionedFileSystem = fs; versionedFileSystem = fs;
@@ -53,7 +53,7 @@ public interface DomainFile extends Comparable<DomainFile> {
public final static String READ_ONLY_PROPERTY = "READ_ONLY"; public final static String READ_ONLY_PROPERTY = "READ_ONLY";
/** /**
* Get the name of the StoredObj that is associated with the data. * Get the name of this project file
* @return the name * @return the name
*/ */
public String getName(); public String getName();
@@ -83,7 +83,7 @@ public interface DomainFile extends Comparable<DomainFile> {
public DomainFile setName(String newName) throws InvalidNameException, IOException; public DomainFile setName(String newName) throws InvalidNameException, IOException;
/** /**
* Returns the path name to the domain object. * Returns the full path name to this file
* @return the path name * @return the path name
*/ */
public String getPathname(); public String getPathname();
@@ -114,7 +114,7 @@ public interface DomainFile extends Comparable<DomainFile> {
public ProjectLocator getProjectLocator(); public ProjectLocator getProjectLocator();
/** /**
* Returns content-type string * Returns content-type string for this file
* @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT} * @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT}
* or {@link ContentHandler#UNKNOWN_CONTENT}. * or {@link ContentHandler#UNKNOWN_CONTENT}.
*/ */
@@ -134,8 +134,11 @@ public interface DomainFile extends Comparable<DomainFile> {
/** /**
* Returns changes made to versioned file by others since checkout was performed. * Returns changes made to versioned file by others since checkout was performed.
* NOTE: This method is unable to cope with version issues which may require an
* upgrade.
* @return change set or null * @return change set or null
* @throws VersionException latest version was created with a newer version of software * @throws VersionException latest version was created with a different version of software
* which prevents rapid determination of change set.
* @throws IOException if a folder item access error occurs or change set was * @throws IOException if a folder item access error occurs or change set was
* produced by newer version of software and can not be read * produced by newer version of software and can not be read
*/ */
@@ -426,7 +429,7 @@ public interface DomainFile extends Comparable<DomainFile> {
* @param keep if true, the private database will be renamed with a .keep * @param keep if true, the private database will be renamed with a .keep
* extension. * extension.
* @throws NotConnectedException if shared project and not connected to repository * @throws NotConnectedException if shared project and not connected to repository
* @throws FileInUseException if this file is in-use / checked-out. * @throws FileInUseException if this file is in-use.
* @throws IOException if file is not checked-out or an IO / access error occurs. * @throws IOException if file is not checked-out or an IO / access error occurs.
*/ */
public void undoCheckout(boolean keep) throws IOException; public void undoCheckout(boolean keep) throws IOException;
@@ -549,7 +552,8 @@ public interface DomainFile extends Comparable<DomainFile> {
/** /**
* Get the list of consumers (Objects) for this domain file. * Get the list of consumers (Objects) for this domain file.
* @return empty array list if there are no consumers * @return true if linking is supported allowing a link-file to be created which
* references this file, else false.
*/ */
public List<?> getConsumers(); public List<?> getConsumers();
@@ -593,7 +597,7 @@ public interface DomainFile extends Comparable<DomainFile> {
/** /**
* Returns the length of this domain file. This size is the minimum disk space * Returns the length of this domain file. This size is the minimum disk space
* used for storing this file, but does not account for additional storage space * used for storing this file, but does not account for additional storage space
* used to tracks changes, etc. * used to track changes, etc.
* @return file length * @return file length
* @throws IOException if IO or access error occurs * @throws IOException if IO or access error occurs
*/ */
@@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public ProjectData getProjectData(); public ProjectData getProjectData();
/** /**
* Returns the path name to the domain object. * Returns the full path name to this folder
* @return the path name * @return the path name
*/ */
public String getPathname(); public String getPathname();
@@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable<DomainFolder> {
throws InvalidNameException, IOException, CancelledException; throws InvalidNameException, IOException, CancelledException;
/** /**
* Create a subfolder of this folder. * Create a subfolder within this folder.
* @param folderName sub-folder name * @param folderName sub-folder name
* @return the folder * @return the new folder
* @throws DuplicateFileException if a folder by * @throws DuplicateFileException if a folder by this name already exists
* this name already exists
* @throws InvalidNameException if name is an empty string of if it contains characters other * @throws InvalidNameException if name is an empty string of if it contains characters other
* than alphanumerics. * than alphanumerics.
* @throws IOException if IO or access error occurs * @throws IOException if IO or access error occurs
@@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException; public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException;
/** /**
* Deletes this folder and all of its contents * Deletes this folder, if empty, from the local filesystem
* @throws IOException if IO or access error occurs * @throws IOException if IO or access error occurs
* @throws FolderNotEmptyException Thrown if the subfolder is not empty. * @throws FolderNotEmptyException Thrown if this folder is not empty.
*/ */
public void delete() throws IOException; public void delete() throws IOException;
/** /**
* Move this folder into the newParent folder. If connected to an archive * Move this folder into the newParent folder. If connected to a repository
* this affects both private and repository folders and files. If not * this moves both private and repository folders/files. If not
* connected, only private folders and files are affected. * connected, only private folders/files are moved.
* @param newParent new parent folder within the same project * @param newParent new parent folder within the same project
* @return the newly relocated folder (the original DomainFolder object becomes invalid since * @return the newly relocated folder (the original DomainFolder object becomes invalid since
* it is immutable) * it is immutable)
@@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
* Copy this folder into the newParent folder. * Copy this folder into the newParent folder.
* @param newParent new parent folder * @param newParent new parent folder
* @param monitor the task monitor * @param monitor the task monitor
* @return the copied folder * @return the new copied folder
* @throws DuplicateFileException if a folder or file by * @throws DuplicateFileException if a folder or file by
* this name already exists in the newParent folder * this name already exists in the newParent folder
* @throws IOException thrown if an IO or access error occurs. * @throws IOException thrown if an IO or access error occurs.
@@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable<DomainFolder> {
throws IOException, CancelledException; throws IOException, CancelledException;
/** /**
* Copy this folder into the newParent folder as a link file. Restrictions: * Create a new link-file in the specified newParent which will reference this folder
* (i.e., linked-folder). Restrictions:
* <ul> * <ul>
* <li>Specified newParent must reside within a different project since internal linking is * <li>Specified newParent must reside within a different project since internal linking is
* not currently supported.</li> * not currently supported.</li>
* </ul> * </ul>
* If this folder is associated with a temporary transient project (i.e., not a locally * If this folder is associated with a temporary transient project (i.e., not a locally
* managed project) the generated link will refer to the remote file with a remote * managed project) the generated link will refer to the remote folder with a remote
* Ghidra URL, otherwise a local project storage path will be used. * Ghidra URL, otherwise a local project storage path will be used.
* @param newParent new parent folder * @param newParent new parent folder where link-file is to be created
* @return newly created domain file or null if link use not supported. * @return newly created domain file (i.e., link-file) or null if link use not supported.
* @throws IOException if an IO or access error occurs. * @throws IOException if an IO or access error occurs.
*/ */
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException; public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;
@@ -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.
@@ -20,5 +19,10 @@ package ghidra.framework.model;
* An interface that allows for a callback when a {@link DomainObject} is closed. * An interface that allows for a callback when a {@link DomainObject} is closed.
*/ */
public interface DomainObjectClosedListener { public interface DomainObjectClosedListener {
public void domainObjectClosed();
/**
* Callback indicating that the specified {@link DomainObject} has been closed.
* @param dobj domain object
*/
public void domainObjectClosed(DomainObject dobj);
} }
@@ -191,8 +191,10 @@ public interface ProjectData {
TaskMonitor monitor) throws IOException, CancelledException; TaskMonitor monitor) throws IOException, CancelledException;
/** /**
* Close the project storage associated with this project data object. * Initiate disposal of this project data object. Any files already open will delay
* NOTE: This should not be invoked if this object is utilized by a Project instance. * disposal until they are closed.
* NOTE: This should only be invoked by the controlling object which created/opened this
* instance to avoid premature disposal.
*/ */
public void close(); public void close();
@@ -209,4 +211,18 @@ public interface ProjectData {
*/ */
public void testValidName(String name, boolean isPath) throws InvalidNameException; public void testValidName(String name, boolean isPath) throws InvalidNameException;
/**
* Generate a repository URL which corresponds to this project data if applicable.
* Local private projects will return null;
* @return repository URL which corresponds to this project data or null if not applicable.
*/
public URL getSharedProjectURL();
/**
* Generate a local URL which corresponds to this project data if applicable.
* Remote transient project data will return null;
* @return local URL which corresponds to this project data or null if not applicable.
*/
public URL getLocalProjectURL();
} }
@@ -25,7 +25,7 @@ import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter; import org.jdom.output.XMLOutputter;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.data.TransientDataManager; import ghidra.framework.data.TransientDataManager;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
@@ -58,7 +58,7 @@ public class DefaultProject implements Project {
private DefaultProjectManager projectManager; private DefaultProjectManager projectManager;
private ProjectLocator projectLocator; private ProjectLocator projectLocator;
private ProjectFileManager fileMgr; private DefaultProjectData projectData;
private ToolManagerImpl toolManager; private ToolManagerImpl toolManager;
private boolean changed; // flag for whether the project configuration has changed private boolean changed; // flag for whether the project configuration has changed
@@ -66,7 +66,7 @@ public class DefaultProject implements Project {
private Map<String, SaveState> dataMap = new HashMap<>(); private Map<String, SaveState> dataMap = new HashMap<>();
private Map<String, ToolTemplate> projectConfigMap = new HashMap<>(); private Map<String, ToolTemplate> projectConfigMap = new HashMap<>();
private Map<URL, ProjectFileManager> otherViews = new HashMap<>(); private Map<URL, DefaultProjectData> otherViewsMap = new HashMap<>();
private Set<URL> visibleViews = new HashSet<>(); private Set<URL> visibleViews = new HashSet<>();
private WeakSet<ProjectViewListener> viewListeners = private WeakSet<ProjectViewListener> viewListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet(); WeakDataStructureFactory.createCopyOnWriteWeakSet();
@@ -86,21 +86,10 @@ public class DefaultProject implements Project {
this.projectManager = projectManager; this.projectManager = projectManager;
this.projectLocator = projectLocator; this.projectLocator = projectLocator;
boolean success = false; Msg.info(this, "Creating project: " + projectLocator.toString());
try { projectData = new DefaultProjectData(projectLocator, repository, true);
Msg.info(this, "Creating project: " + projectLocator.toString()); if (!SystemUtilities.isInHeadlessMode()) {
fileMgr = new ProjectFileManager(projectLocator, repository, true); toolManager = new ToolManagerImpl(this);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
} }
initializeNewProject(); initializeNewProject();
} }
@@ -122,28 +111,18 @@ public class DefaultProject implements Project {
this.projectManager = projectManager; this.projectManager = projectManager;
this.projectLocator = projectLocator; this.projectLocator = projectLocator;
boolean success = false; Msg.info(this, "Opening project: " + projectLocator.toString());
try { projectData = new DefaultProjectData(projectLocator, true, resetOwner);
Msg.info(this, "Opening project: " + projectLocator.toString()); if (!SystemUtilities.isInHeadlessMode()) {
fileMgr = new ProjectFileManager(projectLocator, true, resetOwner); toolManager = new ToolManagerImpl(this);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
} }
} }
/** /**
* Constructor for opening a URL-based project * Constructor for opening a URL-based project
* *
* @param connection project connection * @param projectManager the manager of this project
* @param connection project URL connection (not previously used)
* @throws IOException if I/O error occurs. * @throws IOException if I/O error occurs.
*/ */
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection) protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
@@ -151,27 +130,17 @@ public class DefaultProject implements Project {
this.projectManager = projectManager; this.projectManager = projectManager;
boolean success = false; Msg.info(this, "Opening project/repository: " + connection.getURL());
try { projectData = (DefaultProjectData) connection.getProjectData();
Msg.info(this, "Opening project/repository: " + connection.getURL()); if (projectData == null) {
fileMgr = (ProjectFileManager) connection.getProjectData(); throw new IOException("Failed to open project/repository: " + connection.getURL());
if (fileMgr == null) { }
throw new IOException("Failed to open project/repository: " + connection.getURL());
}
projectLocator = fileMgr.getProjectLocator(); projectLocator = projectData.getProjectLocator();
if (!SystemUtilities.isInHeadlessMode()) { if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this); toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
} }
initializeNewProject(); initializeNewProject();
} }
@@ -300,20 +269,20 @@ public class DefaultProject implements Project {
return null; return null;
} }
ProjectFileManager projectData = (ProjectFileManager) c.getProjectData(); DefaultProjectData projectData = (DefaultProjectData) c.getProjectData();
if (projectData == null) { if (projectData == null) {
throw new IOException( throw new IOException(
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url)); "Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
} }
url = projectData.getProjectLocator().getURL(); // transform to repository root URL url = projectData.getProjectLocator().getURL(); // transform to repository root URL
otherViews.put(url, projectData); otherViewsMap.put(url, projectData);
return projectData; return projectData;
} }
@Override @Override
public ProjectData addProjectView(URL url, boolean visible) throws IOException { public ProjectData addProjectView(URL url, boolean visible) throws IOException {
synchronized (otherViews) { synchronized (otherViewsMap) {
if (isClosed) { if (isClosed) {
throw new IOException("project is closed"); throw new IOException("project is closed");
} }
@@ -322,7 +291,7 @@ public class DefaultProject implements Project {
throw new IOException("Invalid Ghidra URL specified: " + url); throw new IOException("Invalid Ghidra URL specified: " + url);
} }
ProjectData projectData = otherViews.get(url); ProjectData projectData = otherViewsMap.get(url);
if (projectData == null) { if (projectData == null) {
projectData = openProjectView(url); projectData = openProjectView(url);
} }
@@ -340,13 +309,13 @@ public class DefaultProject implements Project {
*/ */
@Override @Override
public void removeProjectView(URL url) { public void removeProjectView(URL url) {
synchronized (otherViews) { synchronized (otherViewsMap) {
ProjectFileManager dataMgr = otherViews.remove(url); DefaultProjectData dataMgr = otherViewsMap.remove(url);
if (dataMgr != null) { if (dataMgr != null) {
if (visibleViews.remove(url)) { if (visibleViews.remove(url)) {
notifyVisibleViewRemoved(url); notifyVisibleViewRemoved(url);
} }
dataMgr.dispose(); dataMgr.close();
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url)); Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
changed = true; changed = true;
} }
@@ -401,22 +370,20 @@ public class DefaultProject implements Project {
@Override @Override
public RepositoryAdapter getRepository() { public RepositoryAdapter getRepository() {
return fileMgr.getRepository(); return projectData.getRepository();
} }
@Override @Override
public void close() { public void close() {
synchronized (otherViews) { synchronized (otherViewsMap) {
isClosed = true; isClosed = true;
Iterator<ProjectFileManager> iter = otherViews.values().iterator(); for (DefaultProjectData dataMgr : otherViewsMap.values()) {
while (iter.hasNext()) {
ProjectFileManager dataMgr = iter.next();
if (dataMgr != null) { if (dataMgr != null) {
dataMgr.dispose(); dataMgr.close();
} }
} }
otherViews.clear(); otherViewsMap.clear();
} }
try { try {
@@ -429,7 +396,7 @@ public class DefaultProject implements Project {
} }
} }
finally { finally {
fileMgr.dispose(); projectData.close();
} }
} }
@@ -449,7 +416,7 @@ public class DefaultProject implements Project {
@Override @Override
public void restore() { public void restore() {
// if there is a saved project, restore it // if there is a saved project, restore it
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE); File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
String errorMsg = null; String errorMsg = null;
Throwable error = null; Throwable error = null;
try { try {
@@ -593,7 +560,7 @@ public class DefaultProject implements Project {
try { try {
// save tool state // save tool state
root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE); File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
OutputStream os = new FileOutputStream(saveFile); OutputStream os = new FileOutputStream(saveFile);
Document doc = new Document(root); Document doc = new Document(root);
XMLOutputter xmlOut = new GenericXMLOutputter(); XMLOutputter xmlOut = new GenericXMLOutputter();
@@ -629,15 +596,14 @@ public class DefaultProject implements Project {
@Override @Override
public List<DomainFile> getOpenData() { public List<DomainFile> getOpenData() {
ArrayList<DomainFile> openFiles = new ArrayList<>(); ArrayList<DomainFile> openFiles = new ArrayList<>();
fileMgr.findOpenFiles(openFiles); projectData.findOpenFiles(openFiles);
ProjectData[] viewedProjs = getViewedProjectData(); ProjectData[] viewedProjs = getViewedProjectData();
for (ProjectData viewedProj : viewedProjs) { for (ProjectData viewedProj : viewedProjs) {
((ProjectFileManager) viewedProj).findOpenFiles(openFiles); ((DefaultProjectData) viewedProj).findOpenFiles(openFiles);
} }
List<DomainFile> list = new ArrayList<>(); List<DomainFile> list = new ArrayList<>();
TransientDataManager.getTransients(list); TransientDataManager.getTransients(list);
for (int i = 0; i < list.size(); i++) { for (DomainFile df : list) {
DomainFile df = list.get(i);
if (df != null && df.isOpen()) { if (df != null && df.isOpen()) {
openFiles.add(df); openFiles.add(df);
} }
@@ -646,8 +612,8 @@ public class DefaultProject implements Project {
} }
@Override @Override
public ProjectFileManager getProjectData() { public DefaultProjectData getProjectData() {
return fileMgr; return projectData;
} }
@Override @Override
@@ -662,12 +628,12 @@ public class DefaultProject implements Project {
@Override @Override
public ProjectData getProjectData(ProjectLocator locator) { public ProjectData getProjectData(ProjectLocator locator) {
if (locator.equals(fileMgr.getProjectLocator())) { if (locator.equals(projectData.getProjectLocator())) {
return fileMgr; return projectData;
} }
synchronized (otherViews) { synchronized (otherViewsMap) {
for (ProjectData data : otherViews.values()) { for (ProjectData data : otherViewsMap.values()) {
if (locator.equals(data.getProjectLocator())) { if (locator.equals(data.getProjectLocator())) {
return data; return data;
} }
@@ -680,30 +646,30 @@ public class DefaultProject implements Project {
@Override @Override
public ProjectData getProjectData(URL url) { public ProjectData getProjectData(URL url) {
if (projectLocator.getURL().equals(url)) { if (projectLocator.getURL().equals(url)) {
return fileMgr; return projectData;
} }
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL(); URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
if (remoteURL != null) { if (remoteURL != null) {
remoteURL = GhidraURL.getProjectURL(url); remoteURL = GhidraURL.getProjectURL(url);
} }
if (remoteURL.equals(url)) { if (remoteURL.equals(url)) {
return fileMgr; return projectData;
} }
synchronized (otherViews) { synchronized (otherViewsMap) {
return otherViews.get(url); return otherViewsMap.get(url);
} }
} }
@Override @Override
public ProjectData[] getViewedProjectData() { public ProjectData[] getViewedProjectData() {
synchronized (otherViews) { synchronized (otherViewsMap) {
// only return visible viewed project // only return visible viewed project
List<ProjectData> list = new ArrayList<>(); List<ProjectData> list = new ArrayList<>();
for (URL url : otherViews.keySet()) { for (URL url : otherViewsMap.keySet()) {
if (visibleViews.contains(url)) { if (visibleViews.contains(url)) {
list.add(otherViews.get(url)); list.add(otherViewsMap.get(url));
} }
} }
@@ -715,11 +681,9 @@ public class DefaultProject implements Project {
@Override @Override
public void releaseFiles(Object consumer) { public void releaseFiles(Object consumer) {
fileMgr.releaseDomainFiles(consumer); projectData.releaseDomainFiles(consumer);
synchronized (otherViews) { synchronized (otherViewsMap) {
Iterator<ProjectFileManager> it = otherViews.values().iterator(); for (DefaultProjectData mgr : otherViewsMap.values()) {
while (it.hasNext()) {
ProjectFileManager mgr = it.next();
mgr.releaseDomainFiles(consumer); mgr.releaseDomainFiles(consumer);
} }
} }
@@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
} }
catch (RepositoryNotFoundException e) { catch (RepositoryNotFoundException e) {
statusCode = StatusCode.NOT_FOUND; statusCode = StatusCode.NOT_FOUND;
return statusCode;
} }
} }
else if (!repositoryServerAdapter.isCancelled()) { else if (!repositoryServerAdapter.isCancelled()) {
@@ -21,7 +21,7 @@ import java.net.URL;
import ghidra.framework.client.NotConnectedException; import ghidra.framework.client.NotConnectedException;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectLocator; import ghidra.framework.model.ProjectLocator;
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode; import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
import ghidra.framework.store.LockException; import ghidra.framework.store.LockException;
@@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
* @return project data instance or null if project not found * @return project data instance or null if project not found
* @throws IOException if IO error occurs * @throws IOException if IO error occurs
*/ */
ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException { DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException {
if (connect(readOnlyAccess) != StatusCode.OK) { if (connect(readOnlyAccess) != StatusCode.OK) {
return null; return null;
} }
try { try {
return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false); return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
} }
catch (NotOwnerException | ReadOnlyException e) { catch (NotOwnerException | ReadOnlyException e) {
statusCode = StatusCode.UNAUTHORIZED; statusCode = StatusCode.UNAUTHORIZED;
@@ -19,7 +19,7 @@ import java.io.*;
import java.net.*; import java.net.*;
import ghidra.framework.client.*; import ghidra.framework.client.*;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectData; import ghidra.framework.model.ProjectData;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@@ -69,38 +69,6 @@ public class GhidraURLConnection extends URLConnection {
} }
} }
// TODO: consider implementing request and response headers
// /**
// * Ghidra Status-Code 200: OK.
// */
// public static final int GHIDRA_OK = 200;
//
// /**
// * Ghidra Status-Code 401: Unauthorized.
// * This response code includes a variety of connection errors
// * which are reported/logged by the Ghidra Server support code.
// */
// public static final int GHIDRA_UNAUTHORIZED = 401;
//
// /**
// * Ghidra Status-Code 404: Not Found.
// */
// public static final int GHIDRA_NOT_FOUND = 404;
//
// /**
// * Ghidra Status-Code 423: Locked
// * Caused by attempt to open local project data with write-access when project is
// * already opened and locked.
// */
// public static final int GHIDRA_LOCKED = 423;
//
// /**
// * Ghidra Status-Code 503: Unavailable
// * Caused by other connection failure
// */
// public static final int GHIDRA_UNAVAILABLE = 503;
/** /**
* Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object. * Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object.
* @see GhidraURLWrappedContent * @see GhidraURLWrappedContent
@@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection {
private GhidraProtocolConnector protocolConnector; private GhidraProtocolConnector protocolConnector;
private ProjectFileManager projectData; private DefaultProjectData projectData;
private Object refObject; private Object refObject;
private boolean readOnly = true; private boolean readOnly = true;
@@ -270,12 +238,17 @@ public class GhidraURLConnection extends URLConnection {
} }
/** /**
* If URL connects and corresponds to a valid repository, this method * If URL connects and corresponds to a valid repository or local project, this method
* may be used to obtain the associated ProjectData object. The caller is * may be used to obtain the associated ProjectData object. The caller is
* responsible for closing the returned project data when no longer in-use, * responsible for properly {@link ProjectData#close() closing} the returned project data
* failure to do so may prevent release of repository handle to server. * instance when no longer in-use, failure to do so may prevent release of repository handle
* Only a single call to this method is permitted. * to server until process exits. It is important that {@link ProjectData#close()} is
* @return transient project data or null if unavailable * invoked once, and only once, per call to this method to ensure project "use" tracking
* is properly maintained. Improperly invoking the close method on a shared transient
* {@link ProjectData} instance may cause the underlying storage to be prematurely
* disposed.
*
* @return project data which corresponds to this connection or null if unavailable
* @throws IOException if an IO error occurs * @throws IOException if an IO error occurs
*/ */
public ProjectData getProjectData() throws IOException { public ProjectData getProjectData() throws IOException {
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
@@ -43,7 +44,7 @@ public class GhidraURLWrappedContent {
private List<Object> consumers = new ArrayList<Object>(); private List<Object> consumers = new ArrayList<Object>();
private ProjectData projectData; private DefaultProjectData projectData;
private Object refObject; private Object refObject;
public GhidraURLWrappedContent(GhidraURLConnection c) { public GhidraURLWrappedContent(GhidraURLConnection c) {
@@ -82,6 +83,8 @@ public class GhidraURLWrappedContent {
/** /**
* Close associated {@link ProjectData} when all consumers have released wrapped object. * Close associated {@link ProjectData} when all consumers have released wrapped object.
* Underlying project data instance may remain active until all open project files have been
* released/closed.
*/ */
private void closeProjectData() { private void closeProjectData() {
if (projectData != null) { if (projectData != null) {
@@ -91,8 +94,8 @@ public class GhidraURLWrappedContent {
refObject = null; refObject = null;
} }
private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException, private DomainFolder getExplicitFolder(String folderPath)
IOException { throws InvalidNameException, IOException {
DomainFolder folder = projectData.getRootFolder(); DomainFolder folder = projectData.getRootFolder();
for (String name : folderPath.substring(1).split("/")) { for (String name : folderPath.substring(1).split("/")) {
DomainFolder subfolder = folder.getFolder(name); DomainFolder subfolder = folder.getFolder(name);
@@ -110,7 +113,7 @@ public class GhidraURLWrappedContent {
return; return;
} }
projectData = c.getProjectData(); projectData = (DefaultProjectData) c.getProjectData();
String folderItemName = c.getFolderItemName(); String folderItemName = c.getFolderItemName();
String folderPath = c.getFolderPath(); String folderPath = c.getFolderPath();
@@ -16,10 +16,11 @@
package ghidra.framework.protocol.ghidra; package ghidra.framework.protocol.ghidra;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import generic.timer.GhidraSwinglessTimer; import generic.timer.GhidraSwinglessTimer;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectLocator; import ghidra.framework.model.ProjectLocator;
import ghidra.framework.remote.RepositoryHandle; import ghidra.framework.remote.RepositoryHandle;
import ghidra.framework.store.LockException; import ghidra.framework.store.LockException;
@@ -27,7 +28,7 @@ import ghidra.util.Msg;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
public class TransientProjectData extends ProjectFileManager { public class TransientProjectData extends DefaultProjectData {
private TransientProjectManager dataMgr; private TransientProjectManager dataMgr;
final RepositoryInfo repositoryInfo; final RepositoryInfo repositoryInfo;
@@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager {
} }
stopCleanupTimer(); stopCleanupTimer();
disposed = true; disposed = true;
}
Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " + String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " +
getProjectLocator().getProjectDir()); getProjectLocator().getProjectDir() + ", URL: " + repositoryInfo.getURL();
if (instanceUseCount != 0) {
Msg.error(this, "Premature removal of active" + msgTail);
}
else {
Msg.debug(this, "Removing idle" + msgTail);
}
}
dataMgr.cleanupProjectData(repositoryInfo, this); dataMgr.cleanupProjectData(repositoryInfo, this);
super.dispose(); // disconnects repository super.dispose(); // disconnects repository
// TODO: There could still be open files if they have not been properly released/closed !! // Remove temporary project storage
// NOTE: This could be affected by project files which still remain open
ProjectLocator locator = getProjectLocator(); ProjectLocator locator = getProjectLocator();
FileUtilities.deleteDir(locator.getProjectDir()); FileUtilities.deleteDir(locator.getProjectDir());
locator.getMarkerFile().delete(); locator.getMarkerFile().delete();
} }
@Override @Override
public void dispose() { public void close() {
// prevent normal disposal - rely on finalizer and shutdown hook // prevent normal disposal - rely on finalizer and shutdown hook
synchronized (cleanupTimer) { synchronized (cleanupTimer) {
if (instanceUseCount == 0) { if (instanceUseCount == 0) {
@@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager {
} }
@Override @Override
protected void finalize() throws Throwable { protected void dispose() {
try { // rely on forcedDispose to invoke super.dispose()
forcedDispose();
}
catch (Throwable t) {
// ignore errors during finalize
}
super.finalize();
} }
@Override
public URL getSharedProjectURL() {
return repositoryInfo.getURL();
}
@Override
public URL getLocalProjectURL() {
return null;
}
} }
@@ -71,8 +71,9 @@ public class TransientProjectManager {
} }
private TransientProjectManager() { private TransientProjectManager() {
Runtime.getRuntime().addShutdownHook( Runtime.getRuntime()
new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook")); .addShutdownHook(new Thread((Runnable) () -> dispose(),
"TransientProjectManager Shutdown Hook"));
} }
/** /**
@@ -80,8 +81,6 @@ public class TransientProjectManager {
* connections. WARNING: This method intended for testing only. * connections. WARNING: This method intended for testing only.
*/ */
public synchronized void dispose() { public synchronized void dispose() {
// TODO: server handles may be shared with non-transient projects
TransientProjectData[] projectDataArray = TransientProjectData[] projectDataArray =
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]); repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
for (TransientProjectData projectData : projectDataArray) { for (TransientProjectData projectData : projectDataArray) {
@@ -15,17 +15,16 @@
*/ */
package ghidra.framework.task; package ghidra.framework.task;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
/** /**
* Class for managing a queue of tasks to be executed, one at a time, in priority order. All the * Class for managing a queue of tasks to be executed, one at a time, in priority order. All the
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject * tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
@@ -95,7 +94,8 @@ public class GTaskManager {
domainObject.addCloseListener(new DomainObjectClosedListener() { domainObject.addCloseListener(new DomainObjectClosedListener() {
@Override @Override
public void domainObjectClosed() { public void domainObjectClosed(DomainObject dobj) {
// assert dobj == domainObj
GTaskManagerFactory.domainObjectClosed(domainObject); GTaskManagerFactory.domainObjectClosed(domainObject);
domainObject = null; domainObject = null;
} }
@@ -107,7 +107,7 @@ public class GTaskManager {
* *
* @param task the task to be run. * @param task the task to be run.
* @param priority the priority of the task. Lower numbers are run before higher numbers. * @param priority the priority of the task. Lower numbers are run before higher numbers.
* @param useCurrentGroup. If true, this task will be rolled into the current transaction group * @param useCurrentGroup If true, this task will be rolled into the current transaction group
* if one exists. If false, any open transaction * if one exists. If false, any open transaction
* will be closed and a new transaction will be opened before * will be closed and a new transaction will be opened before
* this task is run. * this task is run.
@@ -680,7 +680,8 @@ public class GTaskManager {
taskListener.taskCompleted(task, result); taskListener.taskCompleted(task, result);
} }
catch (Throwable unexpected) { catch (Throwable unexpected) {
Msg.error(this, "Unexpected exception notifying listener of task completed", unexpected); Msg.error(this, "Unexpected exception notifying listener of task completed",
unexpected);
} }
} }
@@ -705,7 +706,8 @@ public class GTaskManager {
taskListener.taskScheduled(scheduledTask); taskListener.taskScheduled(scheduledTask);
} }
catch (Throwable unexpected) { catch (Throwable unexpected) {
Msg.error(this, "Unexpected exception notifying listener of task scheduled", unexpected); Msg.error(this, "Unexpected exception notifying listener of task scheduled",
unexpected);
} }
} }
@@ -16,6 +16,7 @@
package ghidra.framework.model; package ghidra.framework.model;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.util.List; import java.util.List;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
@@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData {
return null; return null;
} }
@Override
public URL getSharedProjectURL() {
// stub
return null;
}
@Override
public URL getLocalProjectURL() {
// stub
return null;
}
@Override @Override
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) { public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
// stub // stub
@@ -36,7 +36,7 @@ import docking.wizard.WizardPanel;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.archive.RestoreDialog; import ghidra.app.plugin.core.archive.RestoreDialog;
import ghidra.framework.data.GhidraFileData; import ghidra.framework.data.GhidraFileData;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.main.*; import ghidra.framework.main.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.dialog.*; import ghidra.framework.plugintool.dialog.*;
@@ -694,7 +694,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
Project project = env.getProject(); Project project = env.getProject();
program = env.getProgram("WinHelloCPP.exe"); program = env.getProgram("WinHelloCPP.exe");
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData(); DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY); projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY);
// Create other project to be viewed // Create other project to be viewed
@@ -26,7 +26,7 @@ import org.junit.*;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import generic.test.TestUtils; import generic.test.TestUtils;
import ghidra.framework.data.ProjectFileManager; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.store.ItemCheckoutStatus; import ghidra.framework.store.ItemCheckoutStatus;
@@ -276,7 +276,7 @@ public class ProjectInfoFilesystemTest extends AbstractGhidraHeadedIntegrationTe
} }
private void checkProjectInfo(Class<?> filesystemClass, String storageType) { private void checkProjectInfo(Class<?> filesystemClass, String storageType) {
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData(); DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
String msg = "Expected " + filesystemClass.getSimpleName() + ": "; String msg = "Expected " + filesystemClass.getSimpleName() + ": ";
assertTrue(msg + "Local FileSystem", filesystemClass.isInstance( assertTrue(msg + "Local FileSystem", filesystemClass.isInstance(
TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData))); TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData)));

Some files were not shown because too many files have changed in this diff Show More