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.TraceRecorder;
import ghidra.dbg.target.TargetObject;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
@ -664,7 +664,7 @@ public class DebuggerCoordinates {
if (projData == null) {
try {
// FIXME! orphaned instance - transient in nature
projData = new ProjectFileManager(projLoc, false, false);
projData = new DefaultProjectData(projLoc, false, false);
}
catch (NotOwnerException e) {
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.dbg.target.*;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceObjectChangeType;
@ -45,7 +46,7 @@ public class ObjectTreeModel implements DisplaysModified {
}
@Override
public void domainObjectClosed() {
public void domainObjectClosed(DomainObject dobj) {
setTrace(null);
}

View File

@ -94,7 +94,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
}
@Override
public void domainObjectClosed() {
public void domainObjectClosed(DomainObject dobj) {
// assume dobj == program
dispose();
}
@ -353,9 +354,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
* trace, or bogus external libraries in a mapped program, scoring libraries before module
* names should not cause problems.
*/
Comparator<IndexEntry> comparator = byIsLibrary
.thenComparing(byNameSource)
.thenComparing(byFolderUses);
Comparator<IndexEntry> comparator =
byIsLibrary.thenComparing(byNameSource).thenComparing(byFolderUses);
return projectData.getFileByID(entries.stream().max(comparator).get().dfID);
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (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
* limitations under the License.
*/
import java.io.IOException;
import java.lang.reflect.Method;
import ghidra.app.script.GhidraScript;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.Project;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.local.LocalFolderItem;
import java.io.IOException;
import java.lang.reflect.Method;
public class CleanupMergeDatabasesScript extends GhidraScript {
@Override
@ -31,8 +30,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
Project project = state.getProject();
ProjectFileManager fileMgr = (ProjectFileManager) project.getProjectData();
LocalFileSystem fs = (LocalFileSystem) fileMgr.getPrivateFileSystem();
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
LocalFileSystem fs = (LocalFileSystem) projectData.getPrivateFileSystem();
int cnt = cleanupFolder(fs, "/");
@ -61,9 +60,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
}
// fs.getItemNames(folderPath, true)
String[] itemNames =
(String[]) invokeInstanceMethod("getItemNames", fs, new Class[] { String.class,
boolean.class }, new Object[] { folderPath, true });
String[] itemNames = (String[]) invokeInstanceMethod("getItemNames", fs,
new Class[] { String.class, boolean.class }, new Object[] { folderPath, true });
for (String itemName : itemNames) {
if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) {
@ -78,8 +76,9 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
else {
// make sure we get item out of index
//fs.deallocateItemStorage(folderPath, itemName);
invokeInstanceMethod("deallocateItemStorage", fs, new Class[] { String.class,
String.class }, new Object[] { folderPath, itemName });
invokeInstanceMethod("deallocateItemStorage", fs,
new Class[] { String.class, String.class },
new Object[] { folderPath, itemName });
}
++cnt;
}

View File

@ -75,7 +75,16 @@ public class AskScript extends GhidraScript {
}
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?");
println("Domain file: " + domFile.getName());

View File

@ -38,19 +38,24 @@ public class CompareAnalysisScript extends GhidraScript {
if (otherProgram == null) {
return;
}
println("\n\n****** COMPARING FUNCTIONS:\n");
compareFunctions(otherProgram);
println("\n\n****** COMPARING STRINGS:\n");
compareStrings(otherProgram);
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
reportPercentDisassembled(currentProgram);
reportPercentDisassembled(otherProgram);
println("\n\n****** COMPARING SWITCH TABLES:\n");
compareSwitchTables(otherProgram);
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
compareNoReturns(otherProgram);
println("\n\n****** COMPARING ERRORS:\n");
compareErrors(otherProgram);
try {
println("\n\n****** COMPARING FUNCTIONS:\n");
compareFunctions(otherProgram);
println("\n\n****** COMPARING STRINGS:\n");
compareStrings(otherProgram);
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
reportPercentDisassembled(currentProgram);
reportPercentDisassembled(otherProgram);
println("\n\n****** COMPARING SWITCH TABLES:\n");
compareSwitchTables(otherProgram);
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
compareNoReturns(otherProgram);
println("\n\n****** COMPARING ERRORS:\n");
compareErrors(otherProgram);
}
finally {
otherProgram.release(this);
}
}
void compareFunctions(Program otherProgram) {

View File

@ -73,7 +73,7 @@ public class AnalysisStateInfo {
if (stateMap == null) {
stateMap = new HashMap<>();
programStates.put(program, stateMap);
program.addCloseListener(() -> programStates.remove(program));
program.addCloseListener(doa -> programStates.remove(program));
}
stateMap.put(state.getClass(), state);
}

View File

@ -57,7 +57,7 @@ import ghidra.util.task.*;
* Provides support for auto analysis tasks.
* 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.
@ -145,7 +145,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
private AutoAnalysisManager(Program program) {
this.program = program;
eventQueueID = program.createPrivateEventQueue(this, 500);
program.addCloseListener(this);
program.addCloseListener(dobj -> dispose());
initializeAnalyzers();
}
@ -361,11 +361,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
subType == ChangeManager.FUNCTION_CHANGED_RETURN;
}
@Override
public void domainObjectClosed() {
dispose();
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (program == null) {
@ -961,10 +956,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
}
PluginTool anyTool = null;
Iterator<PluginTool> iterator = toolSet.iterator();
while (iterator.hasNext()) {
PluginTool tool = iterator.next();
for (PluginTool tool : toolSet) {
anyTool = tool;
JFrame toolFrame = tool.getToolFrame();
if (toolFrame != null && toolFrame.isActive()) {

View File

@ -33,7 +33,6 @@ import ghidra.app.nav.Navigatable;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.*;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
@ -510,30 +509,19 @@ public class MarkerManager implements MarkerService {
private final AddressColorCache colorCache = new AddressColorCache();
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) {
this.cache = cache;
this.program = program;
/**
* 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
* domain object closing, which works for plain programs, too.
*/
program.addCloseListener(closeListener);
program.addCloseListener(dobj -> cache.programClosed(program));
}
void clearColors() {
colorCache.clear();
}
private void programClosed() {
program.removeCloseListener(closeListener);
cache.programClosed(program);
}
MarkerSetImpl getByName(String name) {
for (MarkerSetImpl set : markerSets) {
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 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
* @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) {
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
* value can be found in script args or a properties file
* @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
*
* @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) {
@ -2513,7 +2517,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
public Address askAddress(String title, String message) throws CancelledException {
return askAddress(title, message, null);
}
/**
* 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.
@ -2550,15 +2554,16 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address
* 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);
Address defaultAddr = null;
if (defaultValue != null) {
defaultAddr = currentProgram.getAddressFactory().getAddress(defaultValue);
}
// if defaultAddr is null then it assumes no default value
Address existingValue = loadAskValue(defaultAddr, this::parseAddress, key);
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
* 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 IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled
@ -2693,33 +2703,34 @@ public abstract class GhidraScript extends FlatProgramAPI {
public Program askProgram(String title)
throws VersionException, IOException, CancelledException {
DomainFile existingValue = loadAskValue(this::parseDomainFile, title);
if (isRunningHeadless()) {
return (Program) existingValue.getDomainObject(this, false, false, monitor);
DomainFile choice = loadAskValue(this::parseDomainFile, title);
if (!isRunningHeadless()) {
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) {
return null;
}
Program p = (Program) choice.getDomainObject(this, false, false, monitor);
PluginTool tool = state.getTool();
if (tool == null) {
return (Program) choice.getDomainObject(this, false, false, monitor);
return p;
}
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
* 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
* @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 {
@ -3015,8 +3026,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
throw new ImproperUseException(
"The askPassword() method can only be used when running headed Ghidra.");
}
PasswordDialog dialog =
new PasswordDialog(title, null, null, prompt, null, null);
PasswordDialog dialog = new PasswordDialog(title, null, null, prompt, null, null);
try {
state.getTool().showDialog(dialog);
if (!dialog.okWasPressed()) {

View File

@ -878,7 +878,7 @@ public class HeadlessAnalyzer {
// Get parent folder to pass to GhidraScript
File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI())
.getParentFile();
.getParentFile();
currScript = (GhidraScript) c.getConstructor().newInstance();
currScript.setScriptArgs(scriptArgs);
@ -1575,13 +1575,12 @@ public class HeadlessAnalyzer {
}
else {
if (options.readOnly) {
Msg.info(this, "REPORT: Discarded file import due to readOnly option: " +
loaded);
Msg.info(this,
"REPORT: Discarded file import due to readOnly option: " + loaded);
}
else {
Msg.info(this,
"REPORT: Discarded file import as a result of script " +
"activity or analysis timeout: " + loaded);
Msg.info(this, "REPORT: Discarded file import as a result of script " +
"activity or analysis timeout: " + loaded);
}
}
}
@ -1627,9 +1626,9 @@ public class HeadlessAnalyzer {
}
}
private LoadResults<Program> loadPrograms(File file, String folderPath) throws VersionException,
InvalidNameException, DuplicateNameException, CancelledException, IOException,
LoadException {
private LoadResults<Program> loadPrograms(File file, String folderPath)
throws VersionException, InvalidNameException, DuplicateNameException,
CancelledException, IOException, LoadException {
MessageLog messageLog = new MessageLog();
if (options.loaderClass == null) {

View File

@ -28,6 +28,7 @@ import org.junit.*;
import generic.test.TestUtils;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FileSystemEventManager;
import ghidra.framework.store.local.LocalFileSystem;
@ -37,13 +38,13 @@ import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.task.TaskMonitor;
public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest {
public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest {
private File privateProjectDir;
private File sharedProjectDir;
private FileSystem sharedFS;
private LocalFileSystem privateFS;
private ProjectFileManager fileMgr;
private DefaultProjectData projectData;
private DomainFolder root;
private List<MyEvent> events = new ArrayList<>();
@ -83,9 +84,9 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
true, false, true);
fileMgr = new ProjectFileManager(privateFS, sharedFS);
fileMgr.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
root = fileMgr.getRootFolder();
projectData = new DefaultProjectData(privateFS, sharedFS);
projectData.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
root = projectData.getRootFolder();
flushFileSystemEventsAndClearTestQueue();
}
@ -97,7 +98,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@After
public void tearDown() {
fileMgr.dispose();
projectData.dispose();
deleteAll(privateProjectDir);
deleteAll(sharedProjectDir);
}
@ -136,9 +137,15 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
}
}
@Test
public void testGetLocalProjectURL() {
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator()),
projectData.getLocalProjectURL());
}
@Test
public void testGetRootFolder() throws Exception {
DomainFolder rootFolder = fileMgr.getRootFolder();
DomainFolder rootFolder = projectData.getRootFolder();
assertEquals("/", rootFolder.getPathname());
assertEquals(3, rootFolder.getFolders().length);
}
@ -146,11 +153,11 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testGetFolder() throws Exception {
DomainFolder rootFolder = fileMgr.getRootFolder();
DomainFolder df1 = fileMgr.getFolder("/");
DomainFolder df2 = fileMgr.getFolder("/a");
DomainFolder df3 = fileMgr.getFolder("/a/y");
DomainFolder df4 = fileMgr.getFolder("/a/x");
DomainFolder rootFolder = projectData.getRootFolder();
DomainFolder df1 = projectData.getFolder("/");
DomainFolder df2 = projectData.getFolder("/a");
DomainFolder df3 = projectData.getFolder("/a/y");
DomainFolder df4 = projectData.getFolder("/a/x");
assertNotNull(rootFolder);
assertEquals(rootFolder, df1);
@ -178,7 +185,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
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
flushFileSystemEventsAndClearTestQueue();
@ -195,18 +202,18 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
assertEventsSize(2);
checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null);
DomainFile df = fileMgr.getFileByID(fileID1);
DomainFile df = projectData.getFileByID(fileID1);
assertNotNull(df);
assertEquals("file1", df.getName());
assertTrue(!df.isVersioned());
df = fileMgr.getFileByID(fileID2);
df = projectData.getFileByID(fileID2);
assertNotNull(df2);
assertEquals("file2", df.getName());
df1.addToVersionControl("", false, TaskMonitor.DUMMY);
df = fileMgr.getFileByID(fileID1);
df = projectData.getFileByID(fileID1);
assertNotNull(df1);
assertEquals("file1", df.getName());
assertTrue(df.isVersioned());
@ -216,7 +223,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testFileIndex() throws Exception {
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", fileMgr);
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", projectData);
assertNotNull(fileIndex);
@SuppressWarnings("unchecked")
@ -224,21 +231,21 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
(HashMap<String, String>) getInstanceField("fileIdToPathIndex", fileIndex);
assertNotNull(fileIdToPathIndex);
DomainFolder folder = fileMgr.getFolder("/a");
DomainFolder folder = projectData.getFolder("/a");
DomainFile df1 = createFile(folder, "file1");
String fileID = df1.getFileID();
assertEquals(df1, fileMgr.getFileByID(fileID));
assertEquals(df1, projectData.getFileByID(fileID));
// invalidate folder data to force search
GhidraFolderData rootFolderData = fileMgr.getRootFolderData();
GhidraFolderData rootFolderData = projectData.getRootFolderData();
rootFolderData.dispose();
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
}
@ -246,7 +253,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
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
DomainFolder folder = fileMgr.getFolder("/a");
DomainFolder folder = projectData.getFolder("/a");
DomainFile df1 = createFile(folder, "file1");
String fileID = df1.getFileID();
@ -265,7 +272,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
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
DomainFolder folder = fileMgr.getFolder("/a");
DomainFolder folder = projectData.getFolder("/a");
folder.getFiles(); // visit folder to enable folder change listener
// create shared file /a/file1 and keep checked-out
@ -285,7 +292,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
df1.setName("file2");
DomainFile df2 = fileMgr.getFile("/a/file2");
DomainFile df2 = projectData.getFile("/a/file2");
assertTrue(!fileID.equals(df2.getFileID()));
@ -451,7 +458,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
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();
// exists in localFS so "b" folder should not get created again
@ -495,7 +502,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testRenameFolder6() throws Exception {
DomainFolder aFolder = fileMgr.getFolder("/a");
DomainFolder aFolder = projectData.getFolder("/a");
assertNotNull(aFolder);
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
GhidraFolder folder = (GhidraFolder) fileMgr.getFolder("/a");
GhidraFolder folder = (GhidraFolder) projectData.getFolder("/a");
assertNotNull(folder);
assertTrue(folder.privateExists());
assertFalse(folder.sharedExists());
folder = (GhidraFolder) fileMgr.getFolder("/c/a");
folder = (GhidraFolder) projectData.getFolder("/c/a");
assertNotNull(folder);
assertFalse(folder.privateExists());
assertTrue(folder.sharedExists());

View File

@ -41,7 +41,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
private FileSystem sharedFS;
private LocalFileSystem privateFS;
private ProjectFileManager pfm;
private DefaultProjectData projectData;
private GhidraFolder root;
public GhidraFileTest() {
@ -76,8 +76,8 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
false, false, true);
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
true, false, true);
pfm = new ProjectFileManager(privateFS, sharedFS);
root = pfm.getRootFolder();
projectData = new DefaultProjectData(privateFS, sharedFS);
root = projectData.getRootFolder();
}
@ -88,12 +88,12 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
}
@Test
public void testLocalURL() throws IOException {
public void testGetLocalProjectURL() throws IOException {
createDB(privateFS, "/a", "file1");
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"),
pfm.getFile("/a/file1").getLocalProjectURL("xyz"));
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null),
pfm.getFile("/a/file1").getLocalProjectURL(null));
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", "xyz"),
projectData.getFile("/a/file1").getLocalProjectURL("xyz"));
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", null),
projectData.getFile("/a/file1").getLocalProjectURL(null));
}
@Test
@ -345,7 +345,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
private void refresh() throws IOException {
// refresh everything regardless of visited state
pfm.refresh(true);
projectData.refresh(true);
}
private void deleteAll(File file) {

View File

@ -22,6 +22,8 @@ import java.io.IOException;
import org.junit.*;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@ -31,6 +33,7 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
private LocalFileSystem sharedFS;
private LocalFileSystem privateFS;
private DefaultProjectData projectData;
private GhidraFolder root;
public GhidraFolderTest() {
@ -68,8 +71,8 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
false, false, true);
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
true, false, true);
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS);
root = projectFileManager.getRootFolder();
projectData = new DefaultProjectData(privateFS, sharedFS);
root = projectData.getRootFolder();
}
private void deleteTestFiles() {
@ -82,6 +85,15 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
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
public void testGetFolderNames() throws Exception {
GhidraFolder[] folders = root.getFolders();

View File

@ -15,24 +15,23 @@
*/
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.IOException;
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 {
private File testRootDir;
private File privateProjectDir;
private File sharedProjectDir;
private DomainFolder root;
private Project project;
private LocalFileSystem sharedFS;
private LocalFileSystem privateFS;
@ -52,8 +51,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
true, false, false);
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true,
true, false, false);
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS);
root = projectFileManager.getRootFolder();
DefaultProjectData projectData = new DefaultProjectData(privateFS, sharedFS);
root = projectData.getRootFolder();
}
@After
@ -68,8 +67,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
private void deleteAll(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
deleteAll(files[i]);
for (File file2 : files) {
deleteAll(file2);
}
}
file.delete();

View File

@ -903,8 +903,8 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
//
//@formatter:off
Object projectFileManager = getInstanceField("fileManager", df);
invokeInstanceMethod("setDomainObject", projectFileManager,
Object projectData = getInstanceField("projectData", df);
invokeInstanceMethod("setDomainObject", projectData,
new Class[] { String.class, DomainObjectAdapter.class },
new Object[] { path, program }
);
@ -962,8 +962,7 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
}
}
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree,
true);
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, true);
}
}

View File

@ -15,16 +15,18 @@
*/
package ghidra.framework.project;
import static org.junit.Assert.*;
import java.net.URL;
import org.junit.*;
import generic.test.AbstractGenericTest;
import ghidra.framework.model.Project;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ProjectTestUtils;
import ghidra.test.*;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* 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 {
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_VIEW1 = "TestView1";
private final static String PROJECT_VIEW2 = "TestView2";
@ -52,24 +54,9 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2);
}
/**
* Do the test.
* @param args same args that are passed to RegressionTester.main()
*/
@Test
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...
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close();
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close();
@ -87,12 +74,12 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
// validate the view was added to project
ProjectLocator[] projViews = project.getProjectViews();
for (ProjectLocator projView : projViews) {
System.out.println("added view: " + projView);
Msg.debug(this, "** added view: " + projView);
}
// remove the view...
project.removeProjectView(view);
System.out.println("removed view: " + view);
Msg.debug(this, "** removed view: " + view);
projViews = project.getProjectViews();
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 generic.test.AbstractGTest;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
@ -57,8 +57,8 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
projectLocator = new ProjectLocator(TEMP, "Test");
project = TestProjectManager.get().createProject(projectLocator, null, true);
dataDir =
new File(projectLocator.getProjectDir(), ProjectFileManager.INDEXED_DATA_FOLDER_NAME);
userDir = new File(projectLocator.getProjectDir(), ProjectFileManager.USER_FOLDER_NAME);
new File(projectLocator.getProjectDir(), DefaultProjectData.INDEXED_DATA_FOLDER_NAME);
userDir = new File(projectLocator.getProjectDir(), DefaultProjectData.USER_FOLDER_NAME);
ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY);
df = project.getProjectData()

View File

@ -81,8 +81,8 @@ public class FakeSharedProject {
// Note: this how we share multiple projects
void setVersionedFileSystem(LocalFileSystem fs) {
ProjectFileManager fm = getProjectFileManager();
invokeInstanceMethod("setVersionedFileSystem", fm, argTypes(FileSystem.class), args(fs));
DefaultProjectData pd = getProjectData();
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() {
return (ProjectFileManager) gProject.getProjectData();
public DefaultProjectData getProjectData() {
return (DefaultProjectData) gProject.getProjectData();
}
/**
@ -108,8 +108,8 @@ public class FakeSharedProject {
* @return the root folder of this project
*/
public RootGhidraFolder getRootFolder() {
ProjectFileManager pfm = getProjectFileManager();
return (RootGhidraFolder) pfm.getRootFolder();
DefaultProjectData pd = getProjectData();
return (RootGhidraFolder) pd.getRootFolder();
}
/**
@ -181,6 +181,7 @@ public class FakeSharedProject {
* <li>calling {@link #addDomainFile(String)}</li>
* <li>Adding a versioned file to another project that shares the same repo with this project</li>
* </ul>
* @param parentPath the parent folder path
* @param filename the filename
* @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
* folder if needed
*
* @param parentPath the parent folder path
* @param name the name of the folder to create
* @param path the full path of the folder to create
* @return the created folder
* @throws Exception if there are any exceptions creating the folder
*/
@ -367,7 +367,7 @@ public class FakeSharedProject {
* @see FakeRepository#dispose()
*/
public void dispose() {
ProjectLocator projectLocator = getProjectFileManager().getProjectLocator();
ProjectLocator projectLocator = getProjectData().getProjectLocator();
programManager.disposeOpenPrograms();
gProject.close();
FileUtilities.deleteDir(projectLocator.getProjectDir());
@ -388,7 +388,7 @@ public class FakeSharedProject {
}
ProjectLocator pl = df.getProjectLocator();
ProjectLocator mypl = getProjectFileManager().getProjectLocator();
ProjectLocator mypl = getProjectData().getProjectLocator();
if (!pl.equals(mypl)) {
throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " +
mypl.getName() + "\nYou must call addDomainFile(filename).");
@ -397,9 +397,8 @@ public class FakeSharedProject {
private void waitForFileSystemEvents() {
LocalFileSystem versionedFileSystem = getVersionedFileSystem();
FileSystemEventManager eventManager =
(FileSystemEventManager) TestUtils.getInstanceField("eventManager",
versionedFileSystem);
FileSystemEventManager eventManager = (FileSystemEventManager) TestUtils
.getInstanceField("eventManager", versionedFileSystem);
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
}
@ -444,16 +443,16 @@ public class FakeSharedProject {
}
LocalFileSystem getVersionedFileSystem() {
ProjectFileManager fileManager = getProjectFileManager();
DefaultProjectData projectData = getProjectData();
LocalFileSystem fs =
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", fileManager);
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", projectData);
return fs;
}
void refresh() {
ProjectFileManager fileManager = getProjectFileManager();
DefaultProjectData projectData = getProjectData();
try {
fileManager.refresh(true);
projectData.refresh(true);
}
catch (IOException e) {
// shouldn't happen

View File

@ -33,215 +33,219 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*;
public class MergeTwoProgramsScript extends GhidraScript {
@Override
protected void run() throws Exception {
if ( currentProgram == null ) {
printerr( "Please open a program first!" );
if (currentProgram == null) {
printerr("Please open a program first!");
return;
}
Program otherProgram = askProgram( "Select program from which to merge: " );
Program otherProgram = askProgram("Select program from which to merge: ");
if ( otherProgram == null ) {
printerr( "Please select the other program first!" );
if (otherProgram == null) {
printerr("Please select the other program first!");
return;
}
if ( !currentProgram.getLanguage().equals( otherProgram.getLanguage() ) ) {
printerr( "Incompatible program languages!" );
return;
try {
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);
}
if ( currentProgram.getMemory().intersects( otherProgram.getMemory() ) ) {
printerr( "Memory map of current program must be disjoint from other program!" );
return;
finally {
otherProgram.release(this);
}
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 ) {
monitor.setMessage( "Merging references..." );
private void mergeReferences(Program currProgram, Program otherProgram) {
monitor.setMessage("Merging references...");
ReferenceManager currentReferenceManager = currProgram.getReferenceManager();
ReferenceManager otherReferenceManager = otherProgram.getReferenceManager();
ReferenceIterator otherReferenceIterator = otherReferenceManager.getReferenceIterator( otherProgram.getMinAddress() );
while ( otherReferenceIterator.hasNext() ) {
if ( monitor.isCancelled() ) {
ReferenceIterator otherReferenceIterator =
otherReferenceManager.getReferenceIterator(otherProgram.getMinAddress());
while (otherReferenceIterator.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Reference otherReference = otherReferenceIterator.next();
if ( otherReference.isStackReference() ) {
if (otherReference.isStackReference()) {
continue;
}
currentReferenceManager.addReference( otherReference );
currentReferenceManager.addReference(otherReference);
}
}
private void mergeInstructions( Program currProgram, Program otherProgram ) {
monitor.setMessage( "Merging instructions..." );
private void mergeInstructions(Program currProgram, Program otherProgram) {
monitor.setMessage("Merging instructions...");
Listing currentListing = currProgram.getListing();
Listing otherListing = otherProgram.getListing();
InstructionIterator otherInstructions = otherListing.getInstructions( true );
while ( otherInstructions.hasNext() ) {
if ( monitor.isCancelled() ) {
InstructionIterator otherInstructions = otherListing.getInstructions(true);
while (otherInstructions.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Instruction otherInstruction = otherInstructions.next();
if ( currentListing.isUndefined( otherInstruction.getMinAddress(), otherInstruction.getMaxAddress() ) ) {
disassemble( otherInstruction.getMinAddress() );
if (currentListing.isUndefined(otherInstruction.getMinAddress(),
otherInstruction.getMaxAddress())) {
disassemble(otherInstruction.getMinAddress());
}
}
}
private void mergeEquates( Program currProgram, Program otherProgram ) throws Exception {
monitor.setMessage( "Merging equates..." );
private void mergeEquates(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage("Merging equates...");
EquateTable currentEquateTable = currProgram.getEquateTable();
EquateTable otherEquateTable = otherProgram.getEquateTable();
Iterator<Equate> otherEquates = otherEquateTable.getEquates();
while ( otherEquates.hasNext() ) {
if ( monitor.isCancelled() ) {
while (otherEquates.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Equate otherEquate = otherEquates.next();
Equate currentEquate = currentEquateTable.createEquate( otherEquate.getName(), otherEquate.getValue() );
EquateReference [] otherEquateReferences = otherEquate.getReferences();
for ( EquateReference otherEquateReference : otherEquateReferences ) {
if ( monitor.isCancelled() ) {
Equate currentEquate =
currentEquateTable.createEquate(otherEquate.getName(), otherEquate.getValue());
EquateReference[] otherEquateReferences = otherEquate.getReferences();
for (EquateReference otherEquateReference : otherEquateReferences) {
if (monitor.isCancelled()) {
break;
}
currentEquate.addReference( otherEquateReference.getAddress(), otherEquateReference.getOpIndex() );
currentEquate.addReference(otherEquateReference.getAddress(),
otherEquateReference.getOpIndex());
}
}
}
private void mergeData( Program currProgram, Program otherProgram ) throws Exception {
monitor.setMessage( "Merging data..." );
private void mergeData(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage("Merging data...");
Listing currentListing = currProgram.getListing();
Listing otherListing = otherProgram.getListing();
DataIterator otherDataIterator = otherListing.getDefinedData( true );
while ( otherDataIterator.hasNext() ) {
if ( monitor.isCancelled() ) {
DataIterator otherDataIterator = otherListing.getDefinedData(true);
while (otherDataIterator.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Data otherData = otherDataIterator.next();
if ( currentListing.isUndefined( otherData.getMinAddress(), otherData.getMaxAddress() ) ) {
currentListing.createData( otherData.getMinAddress(), otherData.getDataType() );
if (currentListing.isUndefined(otherData.getMinAddress(), otherData.getMaxAddress())) {
currentListing.createData(otherData.getMinAddress(), otherData.getDataType());
}
}
}
private void mergeComments( Program currProgram, Program otherProgram ) throws Exception {
monitor.setMessage( "Merging comments..." );
int [] commentTypes = {
CodeUnit.EOL_COMMENT,
CodeUnit.PRE_COMMENT,
CodeUnit.POST_COMMENT,
CodeUnit.PLATE_COMMENT,
CodeUnit.REPEATABLE_COMMENT,
};
private void mergeComments(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage("Merging comments...");
int[] commentTypes = { CodeUnit.EOL_COMMENT, CodeUnit.PRE_COMMENT, CodeUnit.POST_COMMENT,
CodeUnit.PLATE_COMMENT, CodeUnit.REPEATABLE_COMMENT, };
Listing currentListing = currProgram.getListing();
Listing otherListing = otherProgram.getListing();
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits( true );
while ( otherCodeUnits.hasNext() ) {
if ( monitor.isCancelled() ) {
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits(true);
while (otherCodeUnits.hasNext()) {
if (monitor.isCancelled()) {
break;
}
CodeUnit otherCodeUnit = otherCodeUnits.next();
for ( int commentType : commentTypes ) {
if ( monitor.isCancelled() ) {
for (int commentType : commentTypes) {
if (monitor.isCancelled()) {
break;
}
String otherComment = otherCodeUnit.getComment( commentType );
if ( otherComment != null ) {
currentListing.setComment( otherCodeUnit.getAddress(), commentType, otherComment );
String otherComment = otherCodeUnit.getComment(commentType);
if (otherComment != null) {
currentListing.setComment(otherCodeUnit.getAddress(), commentType,
otherComment);
}
}
}
}
private void mergeBookmarks( Program currProgram, Program otherProgram ) {
monitor.setMessage( "Merging bookmarks..." );
private void mergeBookmarks(Program currProgram, Program otherProgram) {
monitor.setMessage("Merging bookmarks...");
BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager();
BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager();
Iterator<Bookmark> otherBookmarks = otherBookmarkManager.getBookmarksIterator();
while ( otherBookmarks.hasNext() ) {
if ( monitor.isCancelled() ) {
while (otherBookmarks.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Bookmark otherBookmark = otherBookmarks.next();
currentBookmarkManager.setBookmark( otherBookmark.getAddress(),
otherBookmark.getTypeString(),
otherBookmark.getCategory(),
otherBookmark.getComment() );
currentBookmarkManager.setBookmark(otherBookmark.getAddress(),
otherBookmark.getTypeString(), otherBookmark.getCategory(),
otherBookmark.getComment());
}
}
private void mergeSymbols( Program currProgram, Program otherProgram ) throws Exception {
monitor.setMessage( "Merging symbols..." );
private void mergeSymbols(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage("Merging symbols...");
SymbolTable currentSymbolTable = currProgram.getSymbolTable();
SymbolTable otherSymbolTable = otherProgram.getSymbolTable();
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols( false );
while ( otherSymbols.hasNext() ) {
if ( monitor.isCancelled() ) {
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols(false);
while (otherSymbols.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Symbol otherSymbol = otherSymbols.next();
if ( otherSymbol.isDynamic() ) {
if (otherSymbol.isDynamic()) {
continue;
}
try {
Namespace otherNamespace = otherSymbol.getParentNamespace();
Namespace currentNamespace = mirrorNamespace( currProgram, otherProgram, otherNamespace );
if ( otherSymbol.getSymbolType() == SymbolType.FUNCTION ) {
Function otherFunction = otherProgram.getListing().getFunctionAt( otherSymbol.getAddress() );
currProgram.getListing().createFunction( otherSymbol.getName(),
currentNamespace,
otherFunction.getEntryPoint(),
otherFunction.getBody(),
SourceType.USER_DEFINED );
Namespace currentNamespace =
mirrorNamespace(currProgram, otherProgram, otherNamespace);
if (otherSymbol.getSymbolType() == SymbolType.FUNCTION) {
Function otherFunction =
otherProgram.getListing().getFunctionAt(otherSymbol.getAddress());
currProgram.getListing()
.createFunction(otherSymbol.getName(), currentNamespace,
otherFunction.getEntryPoint(), otherFunction.getBody(),
SourceType.USER_DEFINED);
}
else {
currentSymbolTable.createLabel( otherSymbol.getAddress(),
otherSymbol.getName(),
currentNamespace,
SourceType.USER_DEFINED );
currentSymbolTable.createLabel(otherSymbol.getAddress(), otherSymbol.getName(),
currentNamespace, SourceType.USER_DEFINED);
}
}
catch ( Exception e ) {
printerr( "Unable to create symbol: " + otherSymbol.getName() );
catch (Exception e) {
printerr("Unable to create symbol: " + otherSymbol.getName());
}
}
}
private Namespace mirrorNamespace( Program currProgram, Program otherProgram, Namespace otherNamespace ) throws Exception {
if ( otherNamespace == null ) {
private Namespace mirrorNamespace(Program currProgram, Program otherProgram,
Namespace otherNamespace) throws Exception {
if (otherNamespace == null) {
return currProgram.getGlobalNamespace();
}
SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script!
try {
source = otherNamespace.getSymbol().getSource();
}
catch ( Exception e ) {
catch (Exception e) {
}
return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null,
currProgram, source);
}
private void mergeMemory( Program currProgram, Program otherProgram ) throws Exception {
monitor.setMessage( "Merging memory..." );
private void mergeMemory(Program currProgram, Program otherProgram) throws Exception {
monitor.setMessage("Merging memory...");
Memory otherMemory = otherProgram.getMemory();
MemoryBlock[] otherBlocks = otherMemory.getBlocks();
MessageLog log = new MessageLog();

View File

@ -17,7 +17,6 @@
// data and and then save the session.
//@category Examples.Version Tracking
import java.util.Iterator;
import java.util.List;
import ghidra.app.script.GhidraScript;
@ -32,6 +31,21 @@ import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskLauncher;
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
public void run() throws Exception {
@ -39,9 +53,6 @@ public class AutoVersionTrackingScript extends GhidraScript {
askProjectFolder("Please choose a folder for your Version Tracking session.");
String name = askString("Please enter a Version Tracking session name", "Session Name");
Program sourceProgram;
Program destinationProgram;
boolean isCurrentProgramSourceProg = askYesNo("Current Program 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");
}
if (sourceProgram == null || destinationProgram == null) {
return;
}
// Need to end the script transaction or it interferes with vt things that need locks
end(true);
@ -81,9 +96,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
public static <T extends Plugin> T getPlugin(PluginTool tool, Class<T> c) {
List<Plugin> list = tool.getManagedPlugins();
Iterator<Plugin> it = list.iterator();
while (it.hasNext()) {
Plugin p = it.next();
for (Plugin p : list) {
if (p.getClass() == c) {
return c.cast(p);
}

View File

@ -17,6 +17,9 @@
// data and and then save the session.
//@category Examples.Version Tracking
import java.util.Collection;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.feature.vt.api.correlator.program.*;
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.util.exception.CancelledException;
import java.util.Collection;
import java.util.List;
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
public void run() throws Exception {
DomainFolder folder =
askProjectFolder("Please choose a folder for the session domain object");
String name = askString("Please enter a Version Tracking session name", "Session Name");
Program sourceProgram = askProgram("Please select the source (existing annotated) program");
Program destinationProgram = askProgram("Please select the destination (new) program");
sourceProgram = askProgram("Please select the source (existing annotated) program");
if (sourceProgram == null) {
return;
}
destinationProgram = askProgram("Please select the destination (new) program");
if (destinationProgram == null) {
return;
}
VTSession session =
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
// take address sets, thus implying the entire address space should be used?
AddressSetView sourceAddressSet = sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
AddressSetView sourceAddressSet =
sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
AddressSetView destinationAddressSet =
destinationProgram.getMemory().getLoadedAndInitializedAddressSet();
@ -91,17 +113,16 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram,
VTSession session, PluginTool serviceProvider, VTAssociationManager manager,
AddressSetView sourceAddressSet, AddressSetView destinationAddressSet,
VTProgramCorrelatorFactory factory) throws CancelledException,
VTAssociationStatusException {
VTProgramCorrelatorFactory factory)
throws CancelledException, VTAssociationStatusException {
AddressSetView restrictedSourceAddresses =
excludeAcceptedMatches(session, sourceAddressSet, true);
AddressSetView restrictedDestinationAddresses =
excludeAcceptedMatches(session, destinationAddressSet, false);
VTOptions options = factory.createDefaultOptions();
VTProgramCorrelator correlator =
factory.createCorrelator(serviceProvider, sourceProgram, restrictedSourceAddresses,
destinationProgram, restrictedDestinationAddresses, options);
VTProgramCorrelator correlator = factory.createCorrelator(serviceProvider, sourceProgram,
restrictedSourceAddresses, destinationProgram, restrictedDestinationAddresses, options);
VTMatchSet results = correlator.correlate(session, monitor);
applyMatches(manager, results.getMatches());

View File

@ -29,26 +29,47 @@ import ghidra.util.Msg;
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
protected void run() throws Exception {
Project project = state.getProject();
if (project == null) {
throw new RuntimeException("No project open");
}
// Prompt the user to load the two programs that will be analyzed.
// This will only allow you to select programs from the currently-open
// project in Ghidra, so import them if you haven't already.
Program p1 = askProgram("Program1_Version1");
Program p2 = askProgram("Program1_Version2");
p1 = askProgram("Program1_Version1");
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,
// warn the user.
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;
}
}
// Create a new VT session
createVersionTrackingSession("new session", p1, p2);
@ -67,7 +88,7 @@ public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
println("Did not find exact match for: " + functionName);
}
}
/**
* Returns true if one of the programs is locked.
* <p>

View File

@ -82,7 +82,7 @@ public class AutoVersionTrackingTask extends Task {
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
* tool information needed for this command.
@ -483,8 +483,6 @@ public class AutoVersionTrackingTask extends Task {
continue;
}
// 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
Set<Address> sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor);

View File

@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
return;
}
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
BufferFile bf = null;
boolean success = false;
try {
@ -109,7 +109,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
throws IOException {
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID());
String name = DefaultProjectData.getUserDataFilename(associatedItem.getFileID());
FolderItem item = userFilesystem.getItem(path, name);
if (item != null) {
item.delete(-1, null);
@ -130,7 +130,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
throws IOException, CancelledException {
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
FolderItem item = userfs.getItem(path, name);
if (item == null || !(item instanceof DatabaseItem) ||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {

View File

@ -16,12 +16,14 @@
package ghidra.framework.data;
import java.io.*;
import java.net.URL;
import java.util.*;
import docking.widgets.OptionDialog;
import generic.timer.GhidraSwinglessTimer;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.User;
import ghidra.framework.store.*;
import ghidra.framework.store.FileSystem;
@ -37,9 +39,16 @@ import utilities.util.FileUtilities;
/**
* 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 INDEXED_DATA_FOLDER_NAME = "idata";
public static final String USER_FOLDER_NAME = "user";
@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData {
private RootGhidraFolderData rootFolderData;
private Map<String, DomainObjectAdapter> openDomainObjects =
new HashMap<>();
private Map<String, DomainObjectAdapter> openDomainObjects = new HashMap<>();
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
private ProjectLock projectLock;
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.
* @param localStorageLocator the location of the project
@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData {
* write lock (i.e., project in-use)
* @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 {
this.localStorageLocator = localStorageLocator;
@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData {
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
* lock (i.e., project in-use)
*/
public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository,
public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
boolean isInWritableProject) throws IOException, LockException {
this.localStorageLocator = localStorageLocator;
this.repository = repository;
@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData {
* @param versionedFileSystem an existing versioned file-system
* @throws IOException if an IO error occurs
*/
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
throws IOException {
this.localStorageLocator = new ProjectLocator(null, "Test");
owner = SystemUtilities.getUserName();
@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData {
/**
* 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.
* @return the owner of the project
*/
@ -731,9 +743,8 @@ public class ProjectFileManager implements ProjectData {
@Override
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
TaskMonitor monitor)
throws IOException, CancelledException {
TaskMonitor monitor) throws IOException, CancelledException {
newRepository.connect();
if (!newRepository.isConnected()) {
throw new IOException("new respository not connected");
@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData {
long checkoutId = item.getCheckoutId();
int checkoutVersion = item.getCheckoutVersion();
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout(
df.getParent().getPathname(), df.getName(), checkoutId);
ItemCheckoutStatus otherCheckoutStatus =
newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId);
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
return true;
@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData {
* @throws IOException if IO error occurs
* @throws CancelledException if task cancelled
*/
@Override
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
RepositoryAdapter newRepository, TaskMonitor monitor)
throws IOException, CancelledException {
@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData {
* @throws IOException if IO error occurs
* @throws CancelledException if task cancelled
*/
@Override
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
throws IOException, CancelledException {
List<DomainFile> list = new ArrayList<>();
@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData {
}
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
TaskMonitor monitor)
throws IOException, CancelledException {
TaskMonitor monitor) throws IOException, CancelledException {
for (String name : fileSystem.getItemNames(folderPath)) {
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.
* @param associatedFileID the file id
@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData {
}
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
synchronized (ProjectFileManager.this) {
synchronized (DefaultProjectData.this) {
startReconcileUserDataFiles();
}
});
@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData {
return projectDir;
}
public synchronized boolean isClosed() {
return closed;
}
public synchronized boolean isDisposed() {
return disposed;
}
@Override
public void close() {
synchronized (this) {
if (!closed) {
Msg.debug(this, "Closing ProjectData: " + projectDir);
closed = true;
}
if (inUseCount != 0) {
return; // delay 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) {
if (disposed) {
return;
}
Msg.debug(this, "Disposing ProjectData: " + projectDir);
closed = true;
disposed = true;
if (userDataReconcileTimer != null) {
userDataReconcileTimer.stop();
}
@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData {
/**
* Returns the open domain object (opened for update) for the specified path.
* @param pathname the path name
* @return the domain object
* @return the domain object or null if not open
*/
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
return openDomainObjects.get(pathname);
@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData {
public TaskMonitor getProjectDisposalMonitor() {
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
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (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 {
private ProjectFileManager projectData;
private DefaultProjectData projectData;
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
DomainFileIndex(ProjectFileManager projectData) {
DomainFileIndex(DefaultProjectData projectData) {
this.projectData = projectData;
}

View File

@ -23,14 +23,11 @@ import java.util.*;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.RepositoryItem;
import ghidra.framework.store.ItemCheckoutStatus;
import ghidra.framework.store.Version;
import ghidra.framework.store.*;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.util.InvalidNameException;
import ghidra.util.ReadOnlyException;
@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile {
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
try {
String spec = getPathname().substring(1); // remove leading '/'
if (!StringUtils.isEmpty(ref)) {
spec += "#" + ref;
// Direct URL construction done so that ghidra protocol extension may be supported
String urlStr = sharedProjectURL.toExternalForm();
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) {
// ignore
@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile {
if (properties == null) {
return null;
}
String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME);
String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME);
String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME);
String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME);
if (serverName == null || repoName == 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)) {
return null; // avoid initiating a server connection.
@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile {
return getSharedFileURL(projectURL, ref);
}
Properties properties =
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
return getSharedFileURL(properties, ref);
}
return null;

View File

@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
private volatile boolean eventsEnabled = true;
private Set<DomainObjectClosedListener> closeListeners =
new CopyOnWriteArraySet<DomainObjectClosedListener>();
private Set<DomainObjectClosedListener> closeListeners = new CopyOnWriteArraySet<>();
private ArrayList<Object> consumers;
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
private void notifyCloseListeners() {
for (DomainObjectClosedListener listener : closeListeners) {
listener.domainObjectClosed();
listener.domainObjectClosed(this);
}
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
protected ProjectFileManager fileManager;
protected DefaultProjectData projectData;
private LocalFileSystem fileSystem;
private DomainFolderChangeListener listener;
@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.listener = parent.getChangeListener();
}
public LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
return projectData.getUserFileSystem();
}
private GhidraFileData getFileData() throws FileNotFoundException, IOException {
@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile {
void clearDomainObj() {
String path = getPathname();
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path);
if (doa != null && fileManager.clearDomainObject(getPathname())) {
DomainObjectAdapter doa = projectData.getOpenedDomainObject(path);
if (doa != null && projectData.clearDomainObject(getPathname())) {
listener.domainFileObjectClosed(this, doa);
}
}
@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile {
@Override
public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
return projectData.getProjectLocator();
}
@Override
@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile {
@Override
public DomainObject getOpenedDomainObject(Object consumer) {
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname());
if (domainObj != null) {
if (!domainObj.addConsumer(consumer)) {
fileManager.clearDomainObject(getPathname());
projectData.clearDomainObject(getPathname());
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
}
}
@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile {
@Override
public void save(TaskMonitor monitor) throws IOException, CancelledException {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) {
throw new AssertException("Cannot save, domainObj not open");
}
@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile {
@Override
public boolean canSave() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) {
return false;
}
@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile {
@Override
public ArrayList<?> getConsumers() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) {
return new ArrayList<>();
}
@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile {
@Override
public boolean isChanged() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
return dobj != null && dobj.isChanged();
}
@Override
public boolean isOpen() {
return fileManager.getOpenedDomainObject(getPathname()) != null;
return projectData.getOpenedDomainObject(getPathname()) != null;
}
@Override
@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile {
return false;
}
GhidraFile other = (GhidraFile) obj;
if (fileManager != other.fileManager) {
if (projectData != other.projectData) {
return false;
}
return getPathname().equals(other.getPathname());
@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile {
@Override
public String toString() {
ProjectLocator projectLocator = parent.getProjectData().getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
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.util.List;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.FileSystem;
@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
public class GhidraFolder implements DomainFolder {
private ProjectFileManager fileManager;
private DefaultProjectData projectData;
private LocalFileSystem fileSystem;
private FileSystem versionedFileSystem;
private DomainFolderChangeListener listener;
@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder {
private GhidraFolder parent;
private String name;
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
this.fileManager = fileManager;
this.fileSystem = fileManager.getLocalFileSystem();
this.versionedFileSystem = fileManager.getVersionedFileSystem();
GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
this.projectData = projectData;
this.fileSystem = projectData.getLocalFileSystem();
this.versionedFileSystem = projectData.getVersionedFileSystem();
this.listener = listener;
this.name = FileSystem.SEPARATOR;
}
@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem();
this.listener = parent.getChangeListener();
@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder {
}
LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
return projectData.getUserFileSystem();
}
DomainFolderChangeListener getChangeListener() {
return listener;
}
ProjectFileManager getProjectFileManager() {
return fileManager;
}
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
if (fileData == null) {
@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder {
GhidraFolderData getFolderData() throws FileNotFoundException {
if (parent == null) {
return fileManager.getRootFolderData();
return projectData.getRootFolderData();
}
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
if (folderData == null) {
@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder {
private GhidraFolderData createFolderData(String folderName) throws IOException {
synchronized (fileSystem) {
GhidraFolderData parentData =
parent == null ? fileManager.getRootFolderData() : createFolderData();
parent == null ? projectData.getRootFolderData() : createFolderData();
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
if (folderData == null) {
try {
@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder {
}
private GhidraFolderData createFolderData() throws IOException {
GhidraFolderData rootFolderData = fileManager.getRootFolderData();
GhidraFolderData rootFolderData = projectData.getRootFolderData();
if (parent == null) {
return rootFolderData;
}
@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder {
@Override
public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
return projectData.getProjectLocator();
}
@Override
public ProjectFileManager getProjectData() {
return fileManager;
public DefaultProjectData getProjectData() {
return projectData;
}
String getPathname(String childName) {
@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder {
@Override
public URL getSharedProjectURL() {
ProjectLocator projectLocator = getProjectLocator();
URL projectURL = projectLocator.getURL();
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
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());
URL projectURL = projectData.getSharedProjectURL();
if (projectURL == null) {
return null;
}
try {
// Direct URL construction done so that ghidra protocol extension may be supported
@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder {
@Override
public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null);
}
@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder {
@Override
public boolean isInWritableProject() {
return !getProjectData().getLocalFileSystem().isReadOnly();
return !fileSystem.isReadOnly();
}
@Override
@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder {
}
}
catch (IOException e) {
Msg.error(this, "file error for " + parent.getPathname(fileName), e);
Msg.error(this, "file error for " + getPathname(fileName), e);
}
return null;
}
@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder {
return false;
}
GhidraFolder other = (GhidraFolder) obj;
if (fileManager != other.fileManager) {
if (projectData != other.projectData) {
return false;
}
return getPathname().equals(other.getPathname());
@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder {
@Override
public String toString() {
ProjectLocator projectLocator = fileManager.getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
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.TransientProjectData;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FolderNotEmptyException;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.util.*;
import ghidra.util.exception.*;
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 {
private ProjectFileManager fileManager;
private DefaultProjectData projectData;
/**
* Folder change listener - change events only sent if folder is visited
@ -59,17 +70,24 @@ class GhidraFolderData {
private boolean versionedFolderExists;
/**
* General constructor reserved for root folder use only
* @param fileManager
* @param listener
* General constructor reserved for root folder instantiation
* @param projectData associated project data instance
* @param listener folder change listener
*/
GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
this.fileManager = fileManager;
this.fileSystem = fileManager.getLocalFileSystem();
this.versionedFileSystem = fileManager.getVersionedFileSystem();
GhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
this.projectData = projectData;
this.fileSystem = projectData.getLocalFileSystem();
this.versionedFileSystem = projectData.getVersionedFileSystem();
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 {
if (name == null || name.isEmpty()) {
throw new FileNotFoundException("Bad folder name: blank or null");
@ -77,7 +95,7 @@ class GhidraFolderData {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem();
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() {
return visited;
}
/**
* @return local file system
*/
LocalFileSystem getLocalFileSystem() {
return fileSystem;
}
/**
* @return versioned file system
*/
FileSystem getVersionedFileSystem() {
return versionedFileSystem;
}
/**
* @return local user data file system
*/
LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
return projectData.getUserFileSystem();
}
/**
* @return folder change listener
*/
DomainFolderChangeListener getChangeListener() {
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() {
return fileManager.getProjectLocator();
return projectData.getProjectLocator();
}
/**
* @return this folder's parent folder or null if this is the root folder.
*/
GhidraFolderData getParentData() {
return parent;
}
@ -143,7 +184,7 @@ class GhidraFolderData {
}
}
else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy);
return projectData.getRootFolderData().getFolderPathData(folderPath, lazy);
}
if (folderPath.length() == 0) {
return this;
@ -168,10 +209,26 @@ class GhidraFolderData {
return folderData.getFolderPathData(nextPath, lazy);
}
/**
* Return this folder's name.
* @return the name
*/
String getName() {
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 {
synchronized (fileSystem) {
if (parent == null || fileSystem.isReadOnly()) {
@ -242,6 +299,10 @@ class GhidraFolderData {
return path;
}
/**
* Returns the full path name to this folder
* @return the path name
*/
String getPathname() {
if (parent == null) {
return FileSystem.SEPARATOR;
@ -254,6 +315,10 @@ class GhidraFolderData {
return path;
}
/**
* Determine if this folder contains any sub-folders or domain files.
* @return true if this folder is empty.
*/
boolean isEmpty() {
try {
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() {
try {
refresh(false, false, null); // visited will be true upon return
@ -277,6 +346,10 @@ class GhidraFolderData {
return new ArrayList<>(fileList);
}
/**
* Get the list of names for all subfolders contained within this folder.
* @return list of file names
*/
List<String> getFolderNames() {
try {
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.
* If this folder has been visited listener will be notified with rename
* @param oldName
* Update file list/cache based upon rename of a file.
* If this folder has been visited the listener will be notified with rename
* @param oldFileName file name prior to rename
* @param newFileName file name after rename
*/
void fileRenamed(String oldFileName, String newFileName) {
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) {
GhidraFileData fileData;
synchronized (fileSystem) {
@ -340,7 +422,7 @@ class GhidraFolderData {
* underlying local or versioned file. If this folder has been visited an appropriate
* add/remove/change notification will be provided to the listener.
* NOTE: Move and Rename situations are not handled
* @param fileName
* @param fileName name of file which has changed
*/
void fileChanged(String fileName) {
synchronized (fileSystem) {
@ -389,7 +471,8 @@ class GhidraFolderData {
* 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: 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 {
synchronized (fileSystem) {
@ -404,7 +487,7 @@ class GhidraFolderData {
if (folderData.versionedFolderExists || folderData.folderExists) {
// preserve subfolder data
if (folderData.visited) {
folderData.refresh(true, true, fileManager.getProjectDisposalMonitor());
folderData.refresh(true, true, projectData.getProjectDisposalMonitor());
}
return;
}
@ -429,7 +512,7 @@ class GhidraFolderData {
/**
* Remove and dispose specified subfolder data and notify listener of removal
* if this folder has been visited
* @param folderName
* @param folderName name of folder which was removed
*/
void folderRemoved(String folderName) {
synchronized (fileSystem) {
@ -443,6 +526,9 @@ class GhidraFolderData {
}
}
/**
* Disposes the cached data for this folder and all of its children recursively.
*/
void dispose() {
visited = false;
folderList.clear();
@ -458,7 +544,7 @@ class GhidraFolderData {
// NOTE: clearing the following can cause issues since there may be some residual
// activity/use which will get a NPE
// parent = null;
// fileManager = null;
// projectData = null;
// listener = null;
}
@ -476,7 +562,7 @@ class GhidraFolderData {
* Refresh set of sub-folder names and identify added/removed folders.
* @param recursive recurse into visited subfolders if true
* @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 {
@ -653,7 +739,7 @@ class GhidraFolderData {
* of visited state, if false refresh is lazy and will not be
* performed if a previous refresh set the visited state.
* @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 {
synchronized (fileSystem) {
@ -699,11 +785,11 @@ class GhidraFolderData {
}
/**
* Check for existence of subfolder. If this folder visited, rely on folderList
* @param fileName
* @param doRealCheck if true do not rely on fileList
* @return
* @throws IOException
* Check for existence of subfolder. If this folder has previously been visited,
* rely on the cached folderList.
* @param folderName name of folder to look for
* @return true if folder exists, else false
* @throws IOException if an IO error occurs when checking for folder's existance.
*/
boolean containsFolder(String folderName) throws IOException {
synchronized (fileSystem) {
@ -720,8 +806,8 @@ class GhidraFolderData {
/**
* Create and add new subfolder data object to cache. Data will not be created
* if folder does not exist or an IOException occurs.
* @param folderName
* @return folder data or null
* @param folderName name of folder to be added
* @return folder data or null if folder does not exist
*/
private GhidraFolderData addFolderData(String folderName) {
GhidraFolderData folderData = folderDataCache.get(folderName);
@ -739,7 +825,7 @@ class GhidraFolderData {
/**
* 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
* this case null will be returned
* @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
* @param fileName the name of the file to check for
* @return true if this folder contains the fileName
* @throws IOException
* Check for existence of file. If folder previously visited, rely on fileDataCache
* @param fileName the name of the file to look for
* @return true if this folder contains the fileName, else false
* @throws IOException if an IO error occurs while checking for file existance
*/
public boolean containsFile(String fileName) throws IOException {
synchronized (fileSystem) {
@ -783,9 +869,9 @@ class GhidraFolderData {
/**
* Create and add new file data object to cache. Data will not be created
* if file does not exist or an IOException occurs.
* @param fileName
* @return file data or null
* @throws IOException
* @param fileName name of file
* @return file data or null if not found
* @throws IOException if an IO error occurs while checking for file existance
*/
private GhidraFileData addFileData(String fileName) throws IOException {
GhidraFileData fileData = fileDataCache.get(fileName);
@ -793,7 +879,7 @@ class GhidraFolderData {
try {
fileData = new GhidraFileData(this, fileName);
fileDataCache.put(fileName, fileData);
fileManager.updateFileIndex(fileData);
projectData.updateFileIndex(fileData);
}
catch (FileNotFoundException e) {
// ignore
@ -804,10 +890,11 @@ class GhidraFolderData {
/**
* 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
* this case null will be returned
* @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 {
synchronized (fileSystem) {
@ -822,58 +909,11 @@ class GhidraFolderData {
return null;
}
// // TODO: Examine!
// private void removeFolderX(String folderName) {
// folderList.remove(folderName);
// folderDataCache.remove(folderName);
// 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;
// }
/**
* Get the domain file in this folder with the given fileName.
* @param fileName name of file in this folder to retrieve
* @return domain file or null if there is no file in this folder with the given name.
*/
GhidraFile getDomainFile(String fileName) {
synchronized (fileSystem) {
try {
@ -888,6 +928,11 @@ class GhidraFolderData {
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) {
synchronized (fileSystem) {
try {
@ -902,10 +947,26 @@ class GhidraFolderData {
return null;
}
/**
* @return a {@link DomainFolder} instance which corresponds to this folder
*/
GhidraFolder getDomainFolder() {
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)
throws InvalidNameException, IOException, CancelledException {
synchronized (fileSystem) {
@ -937,9 +998,12 @@ class GhidraFolderData {
throw new IOException("File creation failed for unknown reason");
}
fileManager.setDomainObject(file.getPathname(), doa);
projectData.setDomainObject(file.getPathname(), doa);
doa.setDomainFile(file);
doa.setChanged(false);
projectData.trackDomainFileInUse(doa);
listener.domainFileObjectOpenedForUpdate(file, doa);
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)
throws InvalidNameException, IOException, CancelledException {
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 {
synchronized (fileSystem) {
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 {
synchronized (fileSystem) {
if (fileSystem.isReadOnly()) {
@ -1000,6 +1091,9 @@ class GhidraFolderData {
}
}
/**
* Delete this folder from the local filesystem if empty
*/
void deleteLocalFolderIfEmpty() {
synchronized (fileSystem) {
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 {
synchronized (fileSystem) {
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) {
if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) {
if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) {
// check if projects share a common repository
RepositoryAdapter myRepository = fileManager.getRepository();
RepositoryAdapter otherRepository = folderData.fileManager.getRepository();
RepositoryAdapter myRepository = projectData.getRepository();
RepositoryAdapter otherRepository = folderData.projectData.getRepository();
if (myRepository == null || otherRepository == null ||
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
!myRepository.getName().equals(otherRepository.getName())) {
@ -1105,20 +1218,30 @@ class GhidraFolderData {
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 {
synchronized (fileSystem) {
if (newParentData.fileSystem.isReadOnly()) {
if (newParent.fileSystem.isReadOnly()) {
throw new ReadOnlyException("copyTo permitted to writeable project only");
}
if (isAncestor(newParentData)) {
if (isAncestor(newParent)) {
throw new IOException("self-referencing copy not permitted");
}
GhidraFolderData newFolderData = newParentData.getFolderData(name, false);
GhidraFolderData newFolderData = newParent.getFolderData(name, false);
if (newFolderData == null) {
try {
newFolderData = newParentData.createFolder(name);
newFolderData = newParent.createFolder(name);
}
catch (InvalidNameException 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) {
String linkFilename = name;
if (linkFilename == null) {
if (fileManager instanceof TransientProjectData) {
linkFilename = fileManager.getRepository().getName();
if (projectData instanceof TransientProjectData) {
linkFilename = projectData.getRepository().getName();
}
else {
linkFilename = fileManager.getProjectLocator().getName();
linkFilename = projectData.getProjectLocator().getName();
}
}
return newParentData.copyAsLink(fileManager, getPathname(), linkFilename,
return newParent.copyAsLink(projectData, getPathname(), linkFilename,
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,
LinkHandler<?> lh) throws IOException {
synchronized (fileSystem) {
@ -1167,7 +1314,7 @@ class GhidraFolderData {
throw new ReadOnlyException("copyAsLink permitted to writeable project only");
}
if (sourceProjectData == fileManager) {
if (sourceProjectData == projectData) {
// internal linking not yet supported
Msg.error(this, "Internal file/folder links not yet supported");
return null;
@ -1177,13 +1324,12 @@ class GhidraFolderData {
if (sourceProjectData instanceof TransientProjectData) {
RepositoryAdapter repository = sourceProjectData.getRepository();
ServerInfo serverInfo = repository.getServerInfo();
ghidraUrl =
GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName(), pathname);
ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(),
serverInfo.getPortNumber(), repository.getName(), pathname);
}
else {
ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
if (projectLocator.equals(fileManager.getProjectLocator())) {
if (projectLocator.equals(projectData.getProjectLocator())) {
return null; // local internal linking not supported
}
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 newName = preferredName;
int i = 1;
@ -1242,11 +1396,11 @@ class GhidraFolderData {
@Override
public String toString() {
ProjectLocator projectLocator = fileManager.getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
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
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (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 {
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
super(fileManager, listener);
RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
super(projectData, listener);
}
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (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 {
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
super(fileManager, listener);
RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
super(projectData, listener);
}
@Override
GhidraFolder getDomainFolder() {
return new RootGhidraFolder(getProjectFileManager(), getChangeListener());
return new RootGhidraFolder(getProjectData(), getChangeListener());
}
/**
* Provided for testing use only
* @param fs
* @param fs versioned file system
*/
void setVersionedFileSystem(FileSystem fs) {
versionedFileSystem = fs;

View File

@ -53,7 +53,7 @@ public interface DomainFile extends Comparable<DomainFile> {
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
*/
public String getName();
@ -83,7 +83,7 @@ public interface DomainFile extends Comparable<DomainFile> {
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
*/
public String getPathname();
@ -114,7 +114,7 @@ public interface DomainFile extends Comparable<DomainFile> {
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}
* 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.
* NOTE: This method is unable to cope with version issues which may require an
* upgrade.
* @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
* 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
* extension.
* @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.
*/
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.
* @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();
@ -593,7 +597,7 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* 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 to tracks changes, etc.
* used to track changes, etc.
* @return file length
* @throws IOException if IO or access error occurs
*/

View File

@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public ProjectData getProjectData();
/**
* Returns the path name to the domain object.
* Returns the full path name to this folder
* @return the path name
*/
public String getPathname();
@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable<DomainFolder> {
throws InvalidNameException, IOException, CancelledException;
/**
* Create a subfolder of this folder.
* Create a subfolder within this folder.
* @param folderName sub-folder name
* @return the folder
* @throws DuplicateFileException if a folder by
* this name already exists
* @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
@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable<DomainFolder> {
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 FolderNotEmptyException Thrown if the subfolder is not empty.
* @throws FolderNotEmptyException Thrown if this folder is not empty.
*/
public void delete() throws IOException;
/**
* Move this folder into the newParent folder. If connected to an archive
* this affects both private and repository folders and files. If not
* connected, only private folders and files are affected.
* 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)
@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
* Copy this folder into the newParent folder.
* @param newParent new parent folder
* @param monitor the task monitor
* @return the copied folder
* @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.
@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable<DomainFolder> {
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>
* <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 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.
* @param newParent new parent folder
* @return newly created domain file or null if link use not supported.
* @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.
*/
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (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.
*/
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;
/**
* Close the project storage associated with this project data object.
* NOTE: This should not be invoked if this object is utilized by a Project instance.
* Initiate disposal of this project data object. Any files already open will delay
* 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();
@ -209,4 +211,18 @@ public interface ProjectData {
*/
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 ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.data.TransientDataManager;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
@ -58,7 +58,7 @@ public class DefaultProject implements Project {
private DefaultProjectManager projectManager;
private ProjectLocator projectLocator;
private ProjectFileManager fileMgr;
private DefaultProjectData projectData;
private ToolManagerImpl toolManager;
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, 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 WeakSet<ProjectViewListener> viewListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
@ -86,21 +86,10 @@ public class DefaultProject implements Project {
this.projectManager = projectManager;
this.projectLocator = projectLocator;
boolean success = false;
try {
Msg.info(this, "Creating project: " + projectLocator.toString());
fileMgr = new ProjectFileManager(projectLocator, repository, true);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
Msg.info(this, "Creating project: " + projectLocator.toString());
projectData = new DefaultProjectData(projectLocator, repository, true);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
initializeNewProject();
}
@ -122,28 +111,18 @@ public class DefaultProject implements Project {
this.projectManager = projectManager;
this.projectLocator = projectLocator;
boolean success = false;
try {
Msg.info(this, "Opening project: " + projectLocator.toString());
fileMgr = new ProjectFileManager(projectLocator, true, resetOwner);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
Msg.info(this, "Opening project: " + projectLocator.toString());
projectData = new DefaultProjectData(projectLocator, true, resetOwner);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
}
/**
* 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.
*/
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
@ -151,27 +130,17 @@ public class DefaultProject implements Project {
this.projectManager = projectManager;
boolean success = false;
try {
Msg.info(this, "Opening project/repository: " + connection.getURL());
fileMgr = (ProjectFileManager) connection.getProjectData();
if (fileMgr == null) {
throw new IOException("Failed to open project/repository: " + connection.getURL());
}
Msg.info(this, "Opening project/repository: " + connection.getURL());
projectData = (DefaultProjectData) connection.getProjectData();
if (projectData == null) {
throw new IOException("Failed to open project/repository: " + connection.getURL());
}
projectLocator = fileMgr.getProjectLocator();
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
projectLocator = projectData.getProjectLocator();
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
initializeNewProject();
}
@ -300,20 +269,20 @@ public class DefaultProject implements Project {
return null;
}
ProjectFileManager projectData = (ProjectFileManager) c.getProjectData();
DefaultProjectData projectData = (DefaultProjectData) c.getProjectData();
if (projectData == null) {
throw new IOException(
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
}
url = projectData.getProjectLocator().getURL(); // transform to repository root URL
otherViews.put(url, projectData);
otherViewsMap.put(url, projectData);
return projectData;
}
@Override
public ProjectData addProjectView(URL url, boolean visible) throws IOException {
synchronized (otherViews) {
synchronized (otherViewsMap) {
if (isClosed) {
throw new IOException("project is closed");
}
@ -322,7 +291,7 @@ public class DefaultProject implements Project {
throw new IOException("Invalid Ghidra URL specified: " + url);
}
ProjectData projectData = otherViews.get(url);
ProjectData projectData = otherViewsMap.get(url);
if (projectData == null) {
projectData = openProjectView(url);
}
@ -340,13 +309,13 @@ public class DefaultProject implements Project {
*/
@Override
public void removeProjectView(URL url) {
synchronized (otherViews) {
ProjectFileManager dataMgr = otherViews.remove(url);
synchronized (otherViewsMap) {
DefaultProjectData dataMgr = otherViewsMap.remove(url);
if (dataMgr != null) {
if (visibleViews.remove(url)) {
notifyVisibleViewRemoved(url);
}
dataMgr.dispose();
dataMgr.close();
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
changed = true;
}
@ -401,22 +370,20 @@ public class DefaultProject implements Project {
@Override
public RepositoryAdapter getRepository() {
return fileMgr.getRepository();
return projectData.getRepository();
}
@Override
public void close() {
synchronized (otherViews) {
synchronized (otherViewsMap) {
isClosed = true;
Iterator<ProjectFileManager> iter = otherViews.values().iterator();
while (iter.hasNext()) {
ProjectFileManager dataMgr = iter.next();
for (DefaultProjectData dataMgr : otherViewsMap.values()) {
if (dataMgr != null) {
dataMgr.dispose();
dataMgr.close();
}
}
otherViews.clear();
otherViewsMap.clear();
}
try {
@ -429,7 +396,7 @@ public class DefaultProject implements Project {
}
}
finally {
fileMgr.dispose();
projectData.close();
}
}
@ -449,7 +416,7 @@ public class DefaultProject implements Project {
@Override
public void restore() {
// 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;
Throwable error = null;
try {
@ -593,7 +560,7 @@ public class DefaultProject implements Project {
try {
// save tool 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);
Document doc = new Document(root);
XMLOutputter xmlOut = new GenericXMLOutputter();
@ -629,15 +596,14 @@ public class DefaultProject implements Project {
@Override
public List<DomainFile> getOpenData() {
ArrayList<DomainFile> openFiles = new ArrayList<>();
fileMgr.findOpenFiles(openFiles);
projectData.findOpenFiles(openFiles);
ProjectData[] viewedProjs = getViewedProjectData();
for (ProjectData viewedProj : viewedProjs) {
((ProjectFileManager) viewedProj).findOpenFiles(openFiles);
((DefaultProjectData) viewedProj).findOpenFiles(openFiles);
}
List<DomainFile> list = new ArrayList<>();
TransientDataManager.getTransients(list);
for (int i = 0; i < list.size(); i++) {
DomainFile df = list.get(i);
for (DomainFile df : list) {
if (df != null && df.isOpen()) {
openFiles.add(df);
}
@ -646,8 +612,8 @@ public class DefaultProject implements Project {
}
@Override
public ProjectFileManager getProjectData() {
return fileMgr;
public DefaultProjectData getProjectData() {
return projectData;
}
@Override
@ -662,12 +628,12 @@ public class DefaultProject implements Project {
@Override
public ProjectData getProjectData(ProjectLocator locator) {
if (locator.equals(fileMgr.getProjectLocator())) {
return fileMgr;
if (locator.equals(projectData.getProjectLocator())) {
return projectData;
}
synchronized (otherViews) {
for (ProjectData data : otherViews.values()) {
synchronized (otherViewsMap) {
for (ProjectData data : otherViewsMap.values()) {
if (locator.equals(data.getProjectLocator())) {
return data;
}
@ -680,30 +646,30 @@ public class DefaultProject implements Project {
@Override
public ProjectData getProjectData(URL url) {
if (projectLocator.getURL().equals(url)) {
return fileMgr;
return projectData;
}
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
if (remoteURL != null) {
remoteURL = GhidraURL.getProjectURL(url);
}
if (remoteURL.equals(url)) {
return fileMgr;
return projectData;
}
synchronized (otherViews) {
return otherViews.get(url);
synchronized (otherViewsMap) {
return otherViewsMap.get(url);
}
}
@Override
public ProjectData[] getViewedProjectData() {
synchronized (otherViews) {
synchronized (otherViewsMap) {
// only return visible viewed project
List<ProjectData> list = new ArrayList<>();
for (URL url : otherViews.keySet()) {
for (URL url : otherViewsMap.keySet()) {
if (visibleViews.contains(url)) {
list.add(otherViews.get(url));
list.add(otherViewsMap.get(url));
}
}
@ -715,11 +681,9 @@ public class DefaultProject implements Project {
@Override
public void releaseFiles(Object consumer) {
fileMgr.releaseDomainFiles(consumer);
synchronized (otherViews) {
Iterator<ProjectFileManager> it = otherViews.values().iterator();
while (it.hasNext()) {
ProjectFileManager mgr = it.next();
projectData.releaseDomainFiles(consumer);
synchronized (otherViewsMap) {
for (DefaultProjectData mgr : otherViewsMap.values()) {
mgr.releaseDomainFiles(consumer);
}
}

View File

@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
}
catch (RepositoryNotFoundException e) {
statusCode = StatusCode.NOT_FOUND;
return statusCode;
}
}
else if (!repositoryServerAdapter.isCancelled()) {

View File

@ -21,7 +21,7 @@ import java.net.URL;
import ghidra.framework.client.NotConnectedException;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
import ghidra.framework.store.LockException;
@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
* @return project data instance or null if project not found
* @throws IOException if IO error occurs
*/
ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException {
DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException {
if (connect(readOnlyAccess) != StatusCode.OK) {
return null;
}
try {
return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false);
return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
}
catch (NotOwnerException | ReadOnlyException e) {
statusCode = StatusCode.UNAUTHORIZED;

View File

@ -19,7 +19,7 @@ import java.io.*;
import java.net.*;
import ghidra.framework.client.*;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectData;
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.
* @see GhidraURLWrappedContent
@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection {
private GhidraProtocolConnector protocolConnector;
private ProjectFileManager projectData;
private DefaultProjectData projectData;
private Object refObject;
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
* responsible for closing the returned project data when no longer in-use,
* failure to do so may prevent release of repository handle to server.
* Only a single call to this method is permitted.
* @return transient project data or null if unavailable
* responsible for properly {@link ProjectData#close() closing} the returned project data
* instance when no longer in-use, failure to do so may prevent release of repository handle
* to server until process exits. It is important that {@link ProjectData#close()} is
* 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
*/
public ProjectData getProjectData() throws IOException {

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*;
import ghidra.util.InvalidNameException;
@ -43,7 +44,7 @@ public class GhidraURLWrappedContent {
private List<Object> consumers = new ArrayList<Object>();
private ProjectData projectData;
private DefaultProjectData projectData;
private Object refObject;
public GhidraURLWrappedContent(GhidraURLConnection c) {
@ -82,6 +83,8 @@ public class GhidraURLWrappedContent {
/**
* 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() {
if (projectData != null) {
@ -91,8 +94,8 @@ public class GhidraURLWrappedContent {
refObject = null;
}
private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException,
IOException {
private DomainFolder getExplicitFolder(String folderPath)
throws InvalidNameException, IOException {
DomainFolder folder = projectData.getRootFolder();
for (String name : folderPath.substring(1).split("/")) {
DomainFolder subfolder = folder.getFolder(name);
@ -110,7 +113,7 @@ public class GhidraURLWrappedContent {
return;
}
projectData = c.getProjectData();
projectData = (DefaultProjectData) c.getProjectData();
String folderItemName = c.getFolderItemName();
String folderPath = c.getFolderPath();

View File

@ -16,10 +16,11 @@
package ghidra.framework.protocol.ghidra;
import java.io.IOException;
import java.net.URL;
import generic.timer.GhidraSwinglessTimer;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.remote.RepositoryHandle;
import ghidra.framework.store.LockException;
@ -27,7 +28,7 @@ import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import utilities.util.FileUtilities;
public class TransientProjectData extends ProjectFileManager {
public class TransientProjectData extends DefaultProjectData {
private TransientProjectManager dataMgr;
final RepositoryInfo repositoryInfo;
@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager {
}
stopCleanupTimer();
disposed = true;
}
Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " +
getProjectLocator().getProjectDir());
String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " +
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);
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();
FileUtilities.deleteDir(locator.getProjectDir());
locator.getMarkerFile().delete();
}
@Override
public void dispose() {
public void close() {
// prevent normal disposal - rely on finalizer and shutdown hook
synchronized (cleanupTimer) {
if (instanceUseCount == 0) {
@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager {
}
@Override
protected void finalize() throws Throwable {
try {
forcedDispose();
}
catch (Throwable t) {
// ignore errors during finalize
}
super.finalize();
protected void dispose() {
// rely on forcedDispose to invoke super.dispose()
}
@Override
public URL getSharedProjectURL() {
return repositoryInfo.getURL();
}
@Override
public URL getLocalProjectURL() {
return null;
}
}

View File

@ -71,8 +71,9 @@ public class TransientProjectManager {
}
private TransientProjectManager() {
Runtime.getRuntime().addShutdownHook(
new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook"));
Runtime.getRuntime()
.addShutdownHook(new Thread((Runnable) () -> dispose(),
"TransientProjectManager Shutdown Hook"));
}
/**
@ -80,8 +81,6 @@ public class TransientProjectManager {
* connections. WARNING: This method intended for testing only.
*/
public synchronized void dispose() {
// TODO: server handles may be shared with non-transient projects
TransientProjectData[] projectDataArray =
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
for (TransientProjectData projectData : projectDataArray) {

View File

@ -15,17 +15,16 @@
*/
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.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
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
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
@ -95,7 +94,8 @@ public class GTaskManager {
domainObject.addCloseListener(new DomainObjectClosedListener() {
@Override
public void domainObjectClosed() {
public void domainObjectClosed(DomainObject dobj) {
// assert dobj == domainObj
GTaskManagerFactory.domainObjectClosed(domainObject);
domainObject = null;
}
@ -107,7 +107,7 @@ public class GTaskManager {
*
* @param task the task to be run.
* @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
* will be closed and a new transaction will be opened before
* this task is run.
@ -680,7 +680,8 @@ public class GTaskManager {
taskListener.taskCompleted(task, result);
}
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);
}
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;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import ghidra.framework.client.RepositoryAdapter;
@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData {
return null;
}
@Override
public URL getSharedProjectURL() {
// stub
return null;
}
@Override
public URL getLocalProjectURL() {
// stub
return null;
}
@Override
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
// stub

View File

@ -36,7 +36,7 @@ import docking.wizard.WizardPanel;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.archive.RestoreDialog;
import ghidra.framework.data.GhidraFileData;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.main.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.dialog.*;
@ -694,7 +694,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
Project project = env.getProject();
program = env.getProgram("WinHelloCPP.exe");
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData();
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY);
// Create other project to be viewed

View File

@ -26,7 +26,7 @@ import org.junit.*;
import docking.action.DockingActionIf;
import generic.test.TestUtils;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.store.ItemCheckoutStatus;
@ -276,7 +276,7 @@ public class ProjectInfoFilesystemTest extends AbstractGhidraHeadedIntegrationTe
}
private void checkProjectInfo(Class<?> filesystemClass, String storageType) {
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData();
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
String msg = "Expected " + filesystemClass.getSimpleName() + ": ";
assertTrue(msg + "Local FileSystem", filesystemClass.isInstance(
TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData)));

View File

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