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

View file

@ -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",

View file

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

View file

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

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -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;
} }

View file

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

View file

@ -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) {

View file

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

View file

@ -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()) {

View file

@ -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())) {

View file

@ -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()) {

View file

@ -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) {

View file

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

View file

@ -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) {

View 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();

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

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

View file

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

View file

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

View file

@ -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>

View file

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

View file

@ -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())) {

View file

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

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -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;
} }

View file

@ -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;

View file

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

View file

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

View file

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

View file

@ -24,14 +24,25 @@ import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.protocol.ghidra.TransientProjectData; import ghidra.framework.protocol.ghidra.TransientProjectData;
import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FolderNotEmptyException;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/**
* {@link GhidraFolderData} provides the managed object which represents a project folder that
* corresponds to matched folder paths across both a versioned and private
* filesystem and viewed as a single folder at the project level. This class closely mirrors the
* {@link DomainFolder} interface and is used by the {@link GhidraFolder} implementation; both of which
* represent immutable folder references. Changes made to this folder's name or path are not reflected
* in old {@link DomainFolder} instances and must be re-instantiated following such a change.
* Any long-term retention of {@link DomainFolder} and {@link DomainFile} instances requires an
* appropriate change listener to properly discard/reacquire such instances.
*/
class GhidraFolderData { class GhidraFolderData {
private ProjectFileManager fileManager; private DefaultProjectData projectData;
/** /**
* Folder change listener - change events only sent if folder is visited * Folder change listener - change events only sent if folder is visited
@ -59,17 +70,24 @@ class GhidraFolderData {
private boolean versionedFolderExists; private boolean versionedFolderExists;
/** /**
* General constructor reserved for root folder use only * General constructor reserved for root folder instantiation
* @param fileManager * @param projectData associated project data instance
* @param listener * @param listener folder change listener
*/ */
GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) { GhidraFolderData(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;
} }
/**
* Construct a folder instance with a specified name and a correpsonding parent folder
* @param parent parent folder
* @param name folder name
* @throws FileNotFoundException if folder not found or error occured while checking
* for its existance
*/
GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException { GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException {
if (name == null || name.isEmpty()) { if (name == null || name.isEmpty()) {
throw new FileNotFoundException("Bad folder name: blank or null"); throw new FileNotFoundException("Bad folder name: blank or null");
@ -77,7 +95,7 @@ class GhidraFolderData {
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();
@ -95,36 +113,59 @@ class GhidraFolderData {
} }
/** /**
* Returns true if folder has complete list of children * @return true if folder has complete list of children
*/ */
boolean visited() { boolean visited() {
return visited; return visited;
} }
/**
* @return local file system
*/
LocalFileSystem getLocalFileSystem() { LocalFileSystem getLocalFileSystem() {
return fileSystem; return fileSystem;
} }
/**
* @return versioned file system
*/
FileSystem getVersionedFileSystem() { FileSystem getVersionedFileSystem() {
return versionedFileSystem; return versionedFileSystem;
} }
/**
* @return local user data file system
*/
LocalFileSystem getUserFileSystem() { LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem(); return projectData.getUserFileSystem();
} }
/**
* @return folder change listener
*/
DomainFolderChangeListener getChangeListener() { DomainFolderChangeListener getChangeListener() {
return listener; return listener;
} }
ProjectFileManager getProjectFileManager() { /**
return fileManager; * @return project data instance
*/
DefaultProjectData getProjectData() {
return projectData;
} }
/**
* Get the project locator which identifies the system storage
* are for the local file system and other project related resources.
* @return local project locator
*/
ProjectLocator getProjectLocator() { ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator(); return projectData.getProjectLocator();
} }
/**
* @return this folder's parent folder or null if this is the root folder.
*/
GhidraFolderData getParentData() { GhidraFolderData getParentData() {
return parent; return parent;
} }
@ -143,7 +184,7 @@ class GhidraFolderData {
} }
} }
else if (folderPath.startsWith(FileSystem.SEPARATOR)) { else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy); return projectData.getRootFolderData().getFolderPathData(folderPath, lazy);
} }
if (folderPath.length() == 0) { if (folderPath.length() == 0) {
return this; return this;
@ -168,10 +209,26 @@ class GhidraFolderData {
return folderData.getFolderPathData(nextPath, lazy); return folderData.getFolderPathData(nextPath, lazy);
} }
/**
* Return this folder's name.
* @return the name
*/
String getName() { String getName() {
return name; return name;
} }
/**
* Set the name on this domain folder.
* @param newName domain folder name
* @return renamed domain file (the original DomainFolder object becomes invalid since it is
* immutable)
* @throws InvalidNameException if newName contains illegal characters
* @throws DuplicateFileException if a folder named newName
* already exists in this files domain folder.
* @throws FileInUseException if any file within this folder or its descendants is
* in-use / checked-out.
* @throws IOException thrown if an IO or access error occurs.
*/
GhidraFolder setName(String newName) throws InvalidNameException, IOException { GhidraFolder setName(String newName) throws InvalidNameException, IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
if (parent == null || fileSystem.isReadOnly()) { if (parent == null || fileSystem.isReadOnly()) {
@ -242,6 +299,10 @@ class GhidraFolderData {
return path; return path;
} }
/**
* Returns the full path name to this folder
* @return the path name
*/
String getPathname() { String getPathname() {
if (parent == null) { if (parent == null) {
return FileSystem.SEPARATOR; return FileSystem.SEPARATOR;
@ -254,6 +315,10 @@ class GhidraFolderData {
return path; return path;
} }
/**
* Determine if this folder contains any sub-folders or domain files.
* @return true if this folder is empty.
*/
boolean isEmpty() { boolean isEmpty() {
try { try {
refresh(false, false, null); // visited will be true upon return refresh(false, false, null); // visited will be true upon return
@ -266,6 +331,10 @@ class GhidraFolderData {
} }
} }
/**
* Get the list of names for all files contained within this folder.
* @return list of file names
*/
List<String> getFileNames() { List<String> getFileNames() {
try { try {
refresh(false, false, null); // visited will be true upon return refresh(false, false, null); // visited will be true upon return
@ -277,6 +346,10 @@ class GhidraFolderData {
return new ArrayList<>(fileList); return new ArrayList<>(fileList);
} }
/**
* Get the list of names for all subfolders contained within this folder.
* @return list of file names
*/
List<String> getFolderNames() { List<String> getFolderNames() {
try { try {
refresh(false, false, null); // visited will be true upon return refresh(false, false, null); // visited will be true upon return
@ -289,9 +362,10 @@ class GhidraFolderData {
} }
/** /**
* Update file list/cache based upon rename of file. * Update file list/cache based upon rename of a file.
* If this folder has been visited listener will be notified with rename * If this folder has been visited the listener will be notified with rename
* @param oldName * @param oldFileName file name prior to rename
* @param newFileName file name after rename
*/ */
void fileRenamed(String oldFileName, String newFileName) { void fileRenamed(String oldFileName, String newFileName) {
GhidraFileData fileData; GhidraFileData fileData;
@ -314,6 +388,14 @@ class GhidraFolderData {
} }
} }
/**
* Update file list/cache based upon change of parent for a file.
* If this folder or the newParent has been visited the listener will be notified with add/move
* details.
* @param newParent new parent folder
* @param oldFileName file name prior to move
* @param newFileName file name after move
*/
void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) { void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) {
GhidraFileData fileData; GhidraFileData fileData;
synchronized (fileSystem) { synchronized (fileSystem) {
@ -340,7 +422,7 @@ class GhidraFolderData {
* underlying local or versioned file. If this folder has been visited an appropriate * underlying local or versioned file. If this folder has been visited an appropriate
* add/remove/change notification will be provided to the listener. * add/remove/change notification will be provided to the listener.
* NOTE: Move and Rename situations are not handled * NOTE: Move and Rename situations are not handled
* @param fileName * @param fileName name of file which has changed
*/ */
void fileChanged(String fileName) { void fileChanged(String fileName) {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -389,7 +471,8 @@ class GhidraFolderData {
* visited an appropriate add/remove/change notification will be provided to the listener. * visited an appropriate add/remove/change notification will be provided to the listener.
* NOTE: Care should be taken using this method as all sub-folder cache data may be disposed! * NOTE: Care should be taken using this method as all sub-folder cache data may be disposed!
* NOTE: Move and Rename situations are not handled * NOTE: Move and Rename situations are not handled
* @param folderName * @param folderName name of folder which has changed
* @throws IOException if an IO error occurs during associated refresh
*/ */
void folderChanged(String folderName) throws IOException { void folderChanged(String folderName) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -404,7 +487,7 @@ class GhidraFolderData {
if (folderData.versionedFolderExists || folderData.folderExists) { if (folderData.versionedFolderExists || folderData.folderExists) {
// preserve subfolder data // preserve subfolder data
if (folderData.visited) { if (folderData.visited) {
folderData.refresh(true, true, fileManager.getProjectDisposalMonitor()); folderData.refresh(true, true, projectData.getProjectDisposalMonitor());
} }
return; return;
} }
@ -429,7 +512,7 @@ class GhidraFolderData {
/** /**
* Remove and dispose specified subfolder data and notify listener of removal * Remove and dispose specified subfolder data and notify listener of removal
* if this folder has been visited * if this folder has been visited
* @param folderName * @param folderName name of folder which was removed
*/ */
void folderRemoved(String folderName) { void folderRemoved(String folderName) {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -443,6 +526,9 @@ class GhidraFolderData {
} }
} }
/**
* Disposes the cached data for this folder and all of its children recursively.
*/
void dispose() { void dispose() {
visited = false; visited = false;
folderList.clear(); folderList.clear();
@ -458,7 +544,7 @@ class GhidraFolderData {
// NOTE: clearing the following can cause issues since there may be some residual // NOTE: clearing the following can cause issues since there may be some residual
// activity/use which will get a NPE // activity/use which will get a NPE
// parent = null; // parent = null;
// fileManager = null; // projectData = null;
// listener = null; // listener = null;
} }
@ -476,7 +562,7 @@ class GhidraFolderData {
* Refresh set of sub-folder names and identify added/removed folders. * Refresh set of sub-folder names and identify added/removed folders.
* @param recursive recurse into visited subfolders if true * @param recursive recurse into visited subfolders if true
* @param monitor recursion task monitor - break from recursion if cancelled * @param monitor recursion task monitor - break from recursion if cancelled
* @throws IOException * @throws IOException if an IO error occurs during the refresh
*/ */
private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException { private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException {
@ -653,7 +739,7 @@ class GhidraFolderData {
* of visited state, if false refresh is lazy and will not be * of visited state, if false refresh is lazy and will not be
* performed if a previous refresh set the visited state. * performed if a previous refresh set the visited state.
* @param monitor recursion task monitor - break from recursion if cancelled * @param monitor recursion task monitor - break from recursion if cancelled
* @throws IOException * @throws IOException if an IO error occurs during the refresh
*/ */
void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException { void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -699,11 +785,11 @@ class GhidraFolderData {
} }
/** /**
* Check for existence of subfolder. If this folder visited, rely on folderList * Check for existence of subfolder. If this folder has previously been visited,
* @param fileName * rely on the cached folderList.
* @param doRealCheck if true do not rely on fileList * @param folderName name of folder to look for
* @return * @return true if folder exists, else false
* @throws IOException * @throws IOException if an IO error occurs when checking for folder's existance.
*/ */
boolean containsFolder(String folderName) throws IOException { boolean containsFolder(String folderName) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -720,8 +806,8 @@ class GhidraFolderData {
/** /**
* Create and add new subfolder data object to cache. Data will not be created * Create and add new subfolder data object to cache. Data will not be created
* if folder does not exist or an IOException occurs. * if folder does not exist or an IOException occurs.
* @param folderName * @param folderName name of folder to be added
* @return folder data or null * @return folder data or null if folder does not exist
*/ */
private GhidraFolderData addFolderData(String folderName) { private GhidraFolderData addFolderData(String folderName) {
GhidraFolderData folderData = folderDataCache.get(folderName); GhidraFolderData folderData = folderDataCache.get(folderName);
@ -739,7 +825,7 @@ class GhidraFolderData {
/** /**
* Get folder data for child folder specified by folderName * Get folder data for child folder specified by folderName
* @param folderName * @param folderName name of folder
* @param lazy if true folder will not be searched for if not already discovered - in * @param lazy if true folder will not be searched for if not already discovered - in
* this case null will be returned * this case null will be returned
* @return folder data or null if not found or lazy=true and not yet discovered * @return folder data or null if not found or lazy=true and not yet discovered
@ -763,10 +849,10 @@ class GhidraFolderData {
} }
/** /**
* Check for existence of file. If folder visited, rely on fileDataCache * Check for existence of file. If folder previously visited, rely on fileDataCache
* @param fileName the name of the file to check for * @param fileName the name of the file to look for
* @return true if this folder contains the fileName * @return true if this folder contains the fileName, else false
* @throws IOException * @throws IOException if an IO error occurs while checking for file existance
*/ */
public boolean containsFile(String fileName) throws IOException { public boolean containsFile(String fileName) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -783,9 +869,9 @@ class GhidraFolderData {
/** /**
* Create and add new file data object to cache. Data will not be created * Create and add new file data object to cache. Data will not be created
* if file does not exist or an IOException occurs. * if file does not exist or an IOException occurs.
* @param fileName * @param fileName name of file
* @return file data or null * @return file data or null if not found
* @throws IOException * @throws IOException if an IO error occurs while checking for file existance
*/ */
private GhidraFileData addFileData(String fileName) throws IOException { private GhidraFileData addFileData(String fileName) throws IOException {
GhidraFileData fileData = fileDataCache.get(fileName); GhidraFileData fileData = fileDataCache.get(fileName);
@ -793,7 +879,7 @@ class GhidraFolderData {
try { try {
fileData = new GhidraFileData(this, fileName); fileData = new GhidraFileData(this, fileName);
fileDataCache.put(fileName, fileData); fileDataCache.put(fileName, fileData);
fileManager.updateFileIndex(fileData); projectData.updateFileIndex(fileData);
} }
catch (FileNotFoundException e) { catch (FileNotFoundException e) {
// ignore // ignore
@ -804,10 +890,11 @@ class GhidraFolderData {
/** /**
* Get file data for child specified by fileName * Get file data for child specified by fileName
* @param fileName * @param fileName name of file
* @param lazy if true file will not be searched for if not already discovered - in * @param lazy if true file will not be searched for if not already discovered - in
* this case null will be returned * this case null will be returned
* @return file data or null if not found or lazy=true and not yet discovered * @return file data or null if not found or lazy=true and not yet discovered
* @throws IOException if an IO error occurs while checking for file existance
*/ */
GhidraFileData getFileData(String fileName, boolean lazy) throws IOException { GhidraFileData getFileData(String fileName, boolean lazy) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -822,58 +909,11 @@ class GhidraFolderData {
return null; return null;
} }
// // TODO: Examine! /**
// private void removeFolderX(String folderName) { * Get the domain file in this folder with the given fileName.
// folderList.remove(folderName); * @param fileName name of file in this folder to retrieve
// folderDataCache.remove(folderName); * @return domain file or null if there is no file in this folder with the given name.
// listener.domainFolderRemoved(getDomainFolder(), folderName); */
// }
//
// // TODO: Examine!
// void removeFileX(String fileName) {
// fileList.remove(fileName);
// GhidraFileV2Data fileData = fileDataCache.remove(fileName);
// if (fileData != null) {
// fileData.dispose();
// }
//// TODO: May need to eliminate presence of fileID in callback
// listener.domainFileRemoved(getDomainFolder(), fileName, null /* fileID */);
// }
//
// /**
// * Handle addition of new file. If this folder has been visited, listener
// * will be notified of new file addition or change
// * @param fileName
// * @return
// */
// // TODO: Examine!
// GhidraFile fileAddedX(String fileName) {
// invalidateFile(fileName);
// GhidraFile df = getDomainFile(fileName);
// if (visited) {
// getFileData(fileName, false);
// if (fileList.add(fileName)) {
// listener.domainFileAdded(df);
// }
// else {
// listenerX.domainFileStatusChanged(df, fileID)
// }
// }
// return df;
// }
//
//
// // TODO: Examine!
// private GhidraFolder addFolderX(String folderName) {
// invalidateFolder(folderName, false);
// GhidraFolder folder = getDomainFolder(folderName);
// if (folderList.add(folderName) && visited) {
// listener.domainFolderAdded(folder);
// }
// return folder;
// }
GhidraFile getDomainFile(String fileName) { GhidraFile getDomainFile(String fileName) {
synchronized (fileSystem) { synchronized (fileSystem) {
try { try {
@ -888,6 +928,11 @@ class GhidraFolderData {
return null; return null;
} }
/**
* Get the domain folder in this folder with the given subfolderName.
* @param subfolderName name of subfolder in this folder to retrieve
* @return domain folder or null if there is no file in this folder with the given name.
*/
GhidraFolder getDomainFolder(String subfolderName) { GhidraFolder getDomainFolder(String subfolderName) {
synchronized (fileSystem) { synchronized (fileSystem) {
try { try {
@ -902,10 +947,26 @@ class GhidraFolderData {
return null; return null;
} }
/**
* @return a {@link DomainFolder} instance which corresponds to this folder
*/
GhidraFolder getDomainFolder() { GhidraFolder getDomainFolder() {
return new GhidraFolder(parent.getDomainFolder(), name); return new GhidraFolder(parent.getDomainFolder(), name);
} }
/**
* Add a domain object to this folder.
* @param fileName domain file name
* @param obj domain object to be stored
* @param monitor progress monitor
* @return domain file created as a result of adding
* the domain object to this folder
* @throws DuplicateFileException thrown if the file name already exists
* @throws InvalidNameException if name is an empty string
* or if it contains characters other than alphanumerics.
* @throws IOException if IO or access error occurs
* @throws CancelledException if the user cancels the create.
*/
GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor) GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor)
throws InvalidNameException, IOException, CancelledException { throws InvalidNameException, IOException, CancelledException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -937,9 +998,12 @@ class GhidraFolderData {
throw new IOException("File creation failed for unknown reason"); throw new IOException("File creation failed for unknown reason");
} }
fileManager.setDomainObject(file.getPathname(), doa); projectData.setDomainObject(file.getPathname(), doa);
doa.setDomainFile(file); doa.setDomainFile(file);
doa.setChanged(false); doa.setChanged(false);
projectData.trackDomainFileInUse(doa);
listener.domainFileObjectOpenedForUpdate(file, doa); listener.domainFileObjectOpenedForUpdate(file, doa);
return file; return file;
@ -950,6 +1014,19 @@ class GhidraFolderData {
} }
} }
/**
* Add a new domain file to this folder.
* @param fileName domain file name
* @param packFile packed file containing domain file data
* @param monitor progress monitor
* @return domain file created as a result of adding
* the domain object to this folder
* @throws DuplicateFileException thrown if the file name already exists
* @throws InvalidNameException if name is an empty string
* or if it contains characters other than alphanumerics.
* @throws IOException if IO or access error occurs
* @throws CancelledException if the user cancels the create.
*/
GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor) GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor)
throws InvalidNameException, IOException, CancelledException { throws InvalidNameException, IOException, CancelledException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -969,6 +1046,15 @@ class GhidraFolderData {
} }
} }
/**
* Create a subfolder within this folder.
* @param folderName sub-folder name
* @return the new folder
* @throws DuplicateFileException if a folder by this name already exists
* @throws InvalidNameException if name is an empty string of if it contains characters other
* than alphanumerics.
* @throws IOException if IO or access error occurs
*/
GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException { GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
if (fileSystem.isReadOnly()) { if (fileSystem.isReadOnly()) {
@ -984,6 +1070,11 @@ class GhidraFolderData {
} }
} }
/**
* Deletes this folder, if empty, from the local filesystem
* @throws IOException if IO or access error occurs
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
*/
void delete() throws IOException { void delete() throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
if (fileSystem.isReadOnly()) { if (fileSystem.isReadOnly()) {
@ -1000,6 +1091,9 @@ class GhidraFolderData {
} }
} }
/**
* Delete this folder from the local filesystem if empty
*/
void deleteLocalFolderIfEmpty() { void deleteLocalFolderIfEmpty() {
synchronized (fileSystem) { synchronized (fileSystem) {
try { try {
@ -1018,6 +1112,19 @@ class GhidraFolderData {
} }
} }
/**
* Move this folder into the newParent folder. If connected to a repository
* this moves both private and repository folders/files. If not
* connected, only private folders/files are moved.
* @param newParent new parent folder within the same project
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
* it is immutable)
* @throws DuplicateFileException if a folder with the same name
* already exists in newParent folder.
* @throws FileInUseException if this folder or one of its descendants
* contains a file which is in-use / checked-out.
* @throws IOException thrown if an IO or access error occurs.
*/
GhidraFolder moveTo(GhidraFolderData newParent) throws IOException { GhidraFolder moveTo(GhidraFolderData newParent) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) { if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) {
@ -1084,11 +1191,17 @@ class GhidraFolderData {
} }
} }
/**
* Determine if the specified folder if an ancestor of this folder
* (i.e., parent, grand-parent, etc.).
* @param folderData folder to be checked
* @return true if the specified folder if an ancestor of this folder
*/
boolean isAncestor(GhidraFolderData folderData) { boolean isAncestor(GhidraFolderData folderData) {
if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) { if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) {
// check if projects share a common repository // check if projects share a common repository
RepositoryAdapter myRepository = fileManager.getRepository(); RepositoryAdapter myRepository = projectData.getRepository();
RepositoryAdapter otherRepository = folderData.fileManager.getRepository(); RepositoryAdapter otherRepository = folderData.projectData.getRepository();
if (myRepository == null || otherRepository == null || if (myRepository == null || otherRepository == null ||
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) || !myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
!myRepository.getName().equals(otherRepository.getName())) { !myRepository.getName().equals(otherRepository.getName())) {
@ -1105,20 +1218,30 @@ class GhidraFolderData {
return false; return false;
} }
GhidraFolder copyTo(GhidraFolderData newParentData, TaskMonitor monitor) /**
* Copy this folder into the newParent folder.
* @param newParent new parent folder
* @param monitor the task monitor
* @return the new copied folder
* @throws DuplicateFileException if a folder or file by
* this name already exists in the newParent folder
* @throws IOException thrown if an IO or access error occurs.
* @throws CancelledException if task monitor cancelled operation.
*/
GhidraFolder copyTo(GhidraFolderData newParent, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
synchronized (fileSystem) { synchronized (fileSystem) {
if (newParentData.fileSystem.isReadOnly()) { if (newParent.fileSystem.isReadOnly()) {
throw new ReadOnlyException("copyTo permitted to writeable project only"); throw new ReadOnlyException("copyTo permitted to writeable project only");
} }
if (isAncestor(newParentData)) { if (isAncestor(newParent)) {
throw new IOException("self-referencing copy not permitted"); throw new IOException("self-referencing copy not permitted");
} }
GhidraFolderData newFolderData = newParentData.getFolderData(name, false); GhidraFolderData newFolderData = newParent.getFolderData(name, false);
if (newFolderData == null) { if (newFolderData == null) {
try { try {
newFolderData = newParentData.createFolder(name); newFolderData = newParent.createFolder(name);
} }
catch (InvalidNameException e) { catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e); throw new AssertException("Unexpected error", e);
@ -1144,22 +1267,46 @@ class GhidraFolderData {
} }
} }
DomainFile copyToAsLink(GhidraFolderData newParentData) throws IOException { /**
* Create a new link-file in the specified newParent which will reference this folder
* (i.e., linked-folder). Restrictions:
* <ul>
* <li>Specified newParent must reside within a different project since internal linking is
* not currently supported.</li>
* </ul>
* 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 folder with a remote
* Ghidra URL, otherwise a local project storage path will be used.
* @param newParent new parent folder where link-file is to be created
* @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.
*/
DomainFile copyToAsLink(GhidraFolderData newParent) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
String linkFilename = name; String linkFilename = name;
if (linkFilename == null) { if (linkFilename == null) {
if (fileManager instanceof TransientProjectData) { if (projectData instanceof TransientProjectData) {
linkFilename = fileManager.getRepository().getName(); linkFilename = projectData.getRepository().getName();
} }
else { else {
linkFilename = fileManager.getProjectLocator().getName(); linkFilename = projectData.getProjectLocator().getName();
} }
} }
return newParentData.copyAsLink(fileManager, getPathname(), linkFilename, return newParent.copyAsLink(projectData, getPathname(), linkFilename,
FolderLinkContentHandler.INSTANCE); FolderLinkContentHandler.INSTANCE);
} }
} }
/**
* Create a link-file within this folder. The link-file may correspond to various types of
* content (e.g., Program, Trace, Folder, etc.) based upon specified link handler.
* @param sourceProjectData referenced content project data within which specified path exists.
* @param pathname path of referenced content with source project data
* @param linkFilename name of link-file to be created within this folder.
* @param lh link file handler used to create specific link file.
* @return link-file
* @throws IOException if IO error occurs during link creation
*/
DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename, DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename,
LinkHandler<?> lh) throws IOException { LinkHandler<?> lh) throws IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
@ -1167,7 +1314,7 @@ class GhidraFolderData {
throw new ReadOnlyException("copyAsLink permitted to writeable project only"); throw new ReadOnlyException("copyAsLink permitted to writeable project only");
} }
if (sourceProjectData == fileManager) { if (sourceProjectData == projectData) {
// internal linking not yet supported // internal linking not yet supported
Msg.error(this, "Internal file/folder links not yet supported"); Msg.error(this, "Internal file/folder links not yet supported");
return null; return null;
@ -1177,13 +1324,12 @@ class GhidraFolderData {
if (sourceProjectData instanceof TransientProjectData) { if (sourceProjectData instanceof TransientProjectData) {
RepositoryAdapter repository = sourceProjectData.getRepository(); RepositoryAdapter repository = sourceProjectData.getRepository();
ServerInfo serverInfo = repository.getServerInfo(); ServerInfo serverInfo = repository.getServerInfo();
ghidraUrl = ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(),
GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(), serverInfo.getPortNumber(), repository.getName(), pathname);
repository.getName(), pathname);
} }
else { else {
ProjectLocator projectLocator = sourceProjectData.getProjectLocator(); ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
if (projectLocator.equals(fileManager.getProjectLocator())) { if (projectLocator.equals(projectData.getProjectLocator())) {
return null; // local internal linking not supported return null; // local internal linking not supported
} }
ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null); ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null);
@ -1216,6 +1362,14 @@ class GhidraFolderData {
} }
} }
/**
* Generate a non-conflicting file name for this folder based upon the specified preferred name.
* NOTE: This method is subject to race conditions where returned name could conflict by the
* time it is actually used.
* @param preferredName preferred file name
* @return non-conflicting file name
* @throws IOException if an IO error occurs during file checks
*/
String getTargetName(String preferredName) throws IOException { String getTargetName(String preferredName) throws IOException {
String newName = preferredName; String newName = preferredName;
int i = 1; int i = 1;
@ -1242,11 +1396,11 @@ class GhidraFolderData {
@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();
} }
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -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);
} }
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -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;

View file

@ -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
*/ */

View file

@ -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;

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -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);
} }

View file

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

View file

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

View file

@ -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()) {

View file

@ -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;

View file

@ -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 {

View file

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

View file

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

View file

@ -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) {

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

@ -802,9 +802,9 @@ public class ServerTestUtil {
public static void createRepositoryItem(LocalFileSystem repoFilesystem, String name, public static void createRepositoryItem(LocalFileSystem repoFilesystem, String name,
String folderPath, Program program) throws Exception { String folderPath, Program program) throws Exception {
ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(program); ContentHandler<?> contentHandler = DomainObjectAdapter.getContentHandler(program);
long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name, long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name, program,
program, TaskMonitor.DUMMY); TaskMonitor.DUMMY);
LocalFolderItem item = repoFilesystem.getItem(folderPath, name); LocalFolderItem item = repoFilesystem.getItem(folderPath, name);
if (item == null) { if (item == null) {
throw new IOException("Item not found: " + FileSystem.SEPARATOR + name); throw new IOException("Item not found: " + FileSystem.SEPARATOR + name);