Merge branch 'GP-3551_ghidra1_InternalProjectLinks'
|
@ -15,6 +15,43 @@ applied Ghidra SRE capabilities to a variety of problems that involve analyzing
|
||||||
generating deep insights for NSA analysts who seek a better understanding of potential
|
generating deep insights for NSA analysts who seek a better understanding of potential
|
||||||
vulnerabilities in networks and systems.
|
vulnerabilities in networks and systems.
|
||||||
|
|
||||||
|
# What's coming in Ghidra 11.5
|
||||||
|
This is a preview of what is coming in the future Ghidra 11.5 release.
|
||||||
|
|
||||||
|
**NOTE:** Ghidra Server: The Ghidra 11.5 server is compatible with Ghidra 9.2 and later Ghidra
|
||||||
|
clients although the presence of any newer link-files within a repository may not be handled properly
|
||||||
|
by client versions prior to 11.5 which lack support for the new storage format. Ghidra 11.5 clients
|
||||||
|
which introduce new link-files into a project will not be able to add such files into version
|
||||||
|
control if connected to older Ghidra Server versions.
|
||||||
|
|
||||||
|
## Project Link Files
|
||||||
|
|
||||||
|
Support for link-files within a Ghidra Project has been significantly expanded with this release and
|
||||||
|
with it a new file storage type has been introduced which can create some incompatibilties if projects
|
||||||
|
and repositories containing such files are used by older version of Ghidra or the Ghidra Server.
|
||||||
|
|
||||||
|
Previously only external folder and file links were supported through the use of a Ghidra URL.
|
||||||
|
With 11.5 the ability to establish internal folder and file links has been introduced. The new
|
||||||
|
storage format avoids the use of a database and relies only on a light-weight property file. Internal
|
||||||
|
project links also allow for either absolute or relative links. Due to the fact that Ghidra allows
|
||||||
|
a folder or file to have the same pathname, some abiguities can result. It is highly recommended that
|
||||||
|
the use of conflicting folder and file pathnames be avoided.
|
||||||
|
|
||||||
|
The use of internally linked folders and files allows batch import processing to more accurately
|
||||||
|
reflect the native file-system and its use of symbolic links which allow for the same content to
|
||||||
|
be referenced by multiple paths. Allowing this within a Ghidra project can avoid the potential for
|
||||||
|
importing content multiple times with the different paths and simply import once with additional
|
||||||
|
link-files which reference it. How best to leverage links very much depends on the end-user's
|
||||||
|
needs and project file management preferences. Special care must be taken when defining or
|
||||||
|
traversing link-files to avoid external and circular references.
|
||||||
|
|
||||||
|
Additional Ghidra API methods have been provided or refined on the following classes to leverage
|
||||||
|
link-files: `DomainFolder`, `DomainFile`, `LinkFile`, `LinkHandler`, `DomainFileFilter`,
|
||||||
|
`DomainFileIterator`, etc.
|
||||||
|
|
||||||
|
...TO BE CONTINUED...
|
||||||
|
|
||||||
|
|
||||||
# What's New in Ghidra 11.4
|
# What's New in Ghidra 11.4
|
||||||
This release includes new features, enhancements, performance improvements, quite a few bug fixes,
|
This release includes new features, enhancements, performance improvements, quite a few bug fixes,
|
||||||
and many pull-request contributions. Thanks to all those who have contributed their time, thoughts,
|
and many pull-request contributions. Thanks to all those who have contributed their time, thoughts,
|
||||||
|
|
|
@ -116,10 +116,8 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
|
||||||
? view.getTrace().getFixedProgramView(view.getSnap())
|
? view.getTrace().getFixedProgramView(view.getSnap())
|
||||||
: view;
|
: view;
|
||||||
|
|
||||||
ExporterDialog dialog =
|
ExporterDialog.showExporterDialog(tool, fixed.getDomainFile(), fixed,
|
||||||
new ExporterDialog(tool, fixed.getDomainFile(), fixed,
|
|
||||||
getSelectionFromContext(context));
|
getSelectionFromContext(context));
|
||||||
tool.showDialog(dialog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void activatedCopyIntoCurrentProgram(DebuggerProgramLocationActionContext context) {
|
protected void activatedCopyIntoCurrentProgram(DebuggerProgramLocationActionContext context) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.database.ProgramContentHandler;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
@ -73,7 +74,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
||||||
// TODO: Note language and prefer those from the same processor?
|
// TODO: Note language and prefer those from the same processor?
|
||||||
// Will get difficult with new OBTR, since I'd need a platform
|
// Will get difficult with new OBTR, since I'd need a platform
|
||||||
// There's also the WoW64 issue....
|
// There's also the WoW64 issue....
|
||||||
protected record IndexEntry(String name, String dfID, NameSource source) {}
|
protected record IndexEntry(String name, String dfID, NameSource source) {
|
||||||
|
}
|
||||||
|
|
||||||
protected class ModuleChangeListener
|
protected class ModuleChangeListener
|
||||||
implements DomainObjectListener, DomainObjectClosedListener {
|
implements DomainObjectListener, DomainObjectClosedListener {
|
||||||
|
@ -212,11 +214,14 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
||||||
if (disposed) {
|
if (disposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Program.class.isAssignableFrom(file.getDomainObjectClass())) {
|
// Folder-links and program link-files are not handled. Using content type
|
||||||
return;
|
// to filter is the best way to control this. If program links should be considered
|
||||||
}
|
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
|
||||||
|
// should be used.
|
||||||
|
if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
||||||
addToIndex(file, file.getMetadata());
|
addToIndex(file, file.getMetadata());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void addToIndex(DomainFile file, Map<String, String> metadata) {
|
protected void addToIndex(DomainFile file, Map<String, String> metadata) {
|
||||||
String dfID = file.getFileID();
|
String dfID = file.getFileID();
|
||||||
|
@ -383,8 +388,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
||||||
public DomainFile getBestMatch(TraceModule module, long snap, Program currentProgram,
|
public DomainFile getBestMatch(TraceModule module, long snap, Program currentProgram,
|
||||||
Collection<IndexEntry> entries) {
|
Collection<IndexEntry> entries) {
|
||||||
Address base = module.getBase(snap);
|
Address base = module.getBase(snap);
|
||||||
AddressSpace space = base == null
|
AddressSpace space =
|
||||||
? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
|
base == null ? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
|
||||||
: base.getAddressSpace();
|
: base.getAddressSpace();
|
||||||
return getBestMatch(space, module, snap, currentProgram, entries);
|
return getBestMatch(space, module, snap, currentProgram, entries);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.OPEN;
|
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
|
@ -223,9 +223,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
public void targetWithdrawn(Target target) {
|
public void targetWithdrawn(Target target) {
|
||||||
Swing.runLater(() -> updateCurrentTarget());
|
Swing.runLater(() -> updateCurrentTarget());
|
||||||
boolean save = isSaveTracesByDefault();
|
boolean save = isSaveTracesByDefault();
|
||||||
CompletableFuture<Void> flush = save
|
CompletableFuture<Void> flush = save ? waitUnlockedDebounced(target) : AsyncUtils.nil();
|
||||||
? waitUnlockedDebounced(target)
|
|
||||||
: AsyncUtils.nil();
|
|
||||||
flush.thenRunAsync(() -> {
|
flush.thenRunAsync(() -> {
|
||||||
if (!isAutoCloseOnTerminate()) {
|
if (!isAutoCloseOnTerminate()) {
|
||||||
return;
|
return;
|
||||||
|
@ -416,20 +414,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DataTreeDialog getTraceChooserDialog() {
|
protected DataTreeDialog getTraceChooserDialog() {
|
||||||
|
DomainFileFilter filter = new DefaultDomainFileFilter(Trace.class, false);
|
||||||
DomainFileFilter filter = new DomainFileFilter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean accept(DomainFile df) {
|
|
||||||
return Trace.class.isAssignableFrom(df.getDomainObjectClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean followLinkedFolders() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return new DataTreeDialog(null, OpenTraceAction.NAME, OPEN, filter);
|
return new DataTreeDialog(null, OpenTraceAction.NAME, OPEN, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,11 +439,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeDeadTraces() {
|
public void closeDeadTraces() {
|
||||||
checkCloseTraces(targetService == null
|
checkCloseTraces(targetService == null ? getOpenTraces()
|
||||||
? getOpenTraces()
|
: getOpenTraces().stream().filter(t -> targetService.getTarget(t) == null).toList(),
|
||||||
: getOpenTraces().stream()
|
|
||||||
.filter(t -> targetService.getTarget(t) == null)
|
|
||||||
.toList(),
|
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,8 +772,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
varView.setSnap(snap);
|
varView.setSnap(snap);
|
||||||
varView.setPlatform(coordinates.getPlatform());
|
varView.setPlatform(coordinates.getPlatform());
|
||||||
fireLocationEvent(coordinates, cause);
|
fireLocationEvent(coordinates, cause);
|
||||||
}, cause == ActivationCause.EMU_STATE_EDIT
|
}, cause == ActivationCause.EMU_STATE_EDIT ? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
|
||||||
? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
|
|
||||||
: SwingExecutorService.LATER); // Respect event order
|
: SwingExecutorService.LATER); // Respect event order
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -845,7 +826,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
// TODO: Support upgrading
|
// TODO: Support upgrading
|
||||||
e = new VersionException(e.getVersionIndicator(), false).combine(e);
|
e = new VersionException(e.getVersionIndicator(), false).combine(e);
|
||||||
VersionExceptionHandler.showVersionError(null, file.getName(), file.getContentType(),
|
VersionExceptionHandler.showVersionError(null, file.getName(), file.getContentType(),
|
||||||
"Open", e);
|
"Open", false, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
@ -1069,10 +1050,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
protected void checkCloseTraces(Collection<Trace> traces, boolean noConfirm) {
|
protected void checkCloseTraces(Collection<Trace> traces, boolean noConfirm) {
|
||||||
List<Target> live =
|
List<Target> live =
|
||||||
traces.stream()
|
traces.stream().map(t -> targetService.getTarget(t)).filter(t -> t != null).toList();
|
||||||
.map(t -> targetService.getTarget(t))
|
|
||||||
.filter(t -> t != null)
|
|
||||||
.toList();
|
|
||||||
/**
|
/**
|
||||||
* A provider may be reading a trace, likely via the Swing thread, so schedule this on the
|
* A provider may be reading a trace, likely via the Swing thread, so schedule this on the
|
||||||
* same thread to avoid a ClosedException.
|
* same thread to avoid a ClosedException.
|
||||||
|
|
|
@ -313,7 +313,7 @@ public class DBTraceContentHandler extends DBWithUserDataContentHandler<DBTrace>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getContentTypeDisplayString() {
|
public String getContentTypeDisplayString() {
|
||||||
return "Trace";
|
return TRACE_CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,32 +15,15 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database;
|
package ghidra.trace.database;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.framework.data.LinkHandler;
|
import ghidra.framework.data.LinkHandler;
|
||||||
import ghidra.framework.data.URLLinkObject;
|
|
||||||
import ghidra.framework.model.DomainObject;
|
|
||||||
import ghidra.framework.store.FileSystem;
|
|
||||||
import ghidra.util.InvalidNameException;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
public class DBTraceLinkContentHandler extends LinkHandler<DBTrace> {
|
public class DBTraceLinkContentHandler extends LinkHandler<DBTrace> {
|
||||||
|
|
||||||
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
|
public static DBTraceLinkContentHandler INSTANCE = new DBTraceLinkContentHandler();
|
||||||
|
|
||||||
@Override
|
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
|
||||||
public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
|
|
||||||
DomainObject obj, TaskMonitor monitor)
|
|
||||||
throws IOException, InvalidNameException, CancelledException {
|
|
||||||
if (!(obj instanceof URLLinkObject)) {
|
|
||||||
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
|
|
||||||
}
|
|
||||||
return createFile((URLLinkObject) obj, TRACE_LINK_CONTENT_TYPE, fs, path, name,
|
|
||||||
monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getContentType() {
|
public String getContentType() {
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.machinelearning.functionfinding;
|
package ghidra.machinelearning.functionfinding;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -35,7 +33,7 @@ import docking.widgets.table.GTable;
|
||||||
import docking.widgets.table.threaded.GThreadedTablePanel;
|
import docking.widgets.table.threaded.GThreadedTablePanel;
|
||||||
import docking.widgets.textfield.IntegerTextField;
|
import docking.widgets.textfield.IntegerTextField;
|
||||||
import ghidra.app.services.ProgramManager;
|
import ghidra.app.services.ProgramManager;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.ProgramFileChooser;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.program.model.address.AddressSet;
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
@ -485,11 +483,7 @@ public class FunctionStartRFParamsDialog extends ReusableDialogComponentProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
private void searchOtherProgram(RandomForestRowObject modelRow) {
|
private void searchOtherProgram(RandomForestRowObject modelRow) {
|
||||||
DataTreeDialog dtd =
|
ProgramFileChooser dtd = new ProgramFileChooser(null, "Select Program");
|
||||||
new DataTreeDialog(null, "Select Program", OPEN, f -> {
|
|
||||||
Class<?> c = f.getDomainObjectClass();
|
|
||||||
return Program.class.isAssignableFrom(c);
|
|
||||||
});
|
|
||||||
dtd.show();
|
dtd.show();
|
||||||
DomainFile dFile = dtd.getDomainFile();
|
DomainFile dFile = dtd.getDomainFile();
|
||||||
if (dFile == null) {
|
if (dFile == null) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.net.URL;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.model.DomainFolder;
|
import ghidra.framework.model.DomainFolder;
|
||||||
import ghidra.framework.protocol.ghidra.*;
|
import ghidra.framework.protocol.ghidra.*;
|
||||||
|
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
|
||||||
import ghidra.program.database.ProgramContentHandler;
|
import ghidra.program.database.ProgramContentHandler;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -56,7 +57,8 @@ public abstract class IterateRepository {
|
||||||
throw new MalformedURLException("Unsupported repository URL: " + ghidraURL);
|
throw new MalformedURLException("Unsupported repository URL: " + ghidraURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
GhidraURLQuery.queryUrl(ghidraURL, new GhidraURLResultHandlerAdapter(true) {
|
// Query URL - may be either file or folder (no link following)
|
||||||
|
GhidraURLQuery.queryUrl(ghidraURL, null, new GhidraURLResultHandlerAdapter(true) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processResult(DomainFolder domainFolder, URL url, TaskMonitor m)
|
public void processResult(DomainFolder domainFolder, URL url, TaskMonitor m)
|
||||||
|
@ -76,7 +78,9 @@ public abstract class IterateRepository {
|
||||||
process(domainFile, monitor);
|
process(domainFile, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, monitor);
|
// Link files are skipped to avoid duplicate processing
|
||||||
|
// Processing should be done on actual folder - not a linked folder
|
||||||
|
}, LinkFileControl.NO_FOLLOW, monitor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,12 +119,11 @@ public abstract class IterateRepository {
|
||||||
private void process(DomainFile file, TaskMonitor monitor)
|
private void process(DomainFile file, TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
|
|
||||||
// Do not follow folder-links or consider program links. Using content type
|
// Do not follow folder-links or consider program links to avoid possible duplication of
|
||||||
// to filter is best way to control this. If program links should be considered
|
// file processing. Using content type is the best way to restrict this. If program links
|
||||||
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
|
// should be considered "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
|
||||||
// should be used.
|
// should be used.
|
||||||
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
||||||
// NOTE: linked-folders and linked-files are not currently supported
|
|
||||||
return; // skip non-program file
|
return; // skip non-program file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +132,7 @@ public abstract class IterateRepository {
|
||||||
Msg.debug(IterateRepository.class, "Processing " + file.getPathname() + "...");
|
Msg.debug(IterateRepository.class, "Processing " + file.getPathname() + "...");
|
||||||
monitor.setMessage("Processing: " + file.getName());
|
monitor.setMessage("Processing: " + file.getName());
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
|
// NOTE: The following method invocation will follow all links if presented one
|
||||||
program = (Program) file.getReadOnlyDomainObject(this, -1, monitor);
|
program = (Program) file.getReadOnlyDomainObject(this, -1, monitor);
|
||||||
process(program, monitor);
|
process(program, monitor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,6 +398,10 @@ src/main/help/help/topics/FrontEndPlugin/Project_Info.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/Re-opening_a_Project.htm||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/Re-opening_a_Project.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/Restore_Project.htm||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/Restore_Project.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/Saving_a_Ghidra_Project.htm||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/Saving_a_Ghidra_Project.htm||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteBrokenFileLinkIcon.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteBrokenFolderLinkIcon.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteFileLinkIcon.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteFolderLinkIcon.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/images/ArchiveFileExists.png||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/images/ArchiveFileExists.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/images/ArchiveProject.png||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/images/ArchiveProject.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/images/ChangeAccessList.png||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/images/ChangeAccessList.png||GHIDRA||||END|
|
||||||
|
@ -439,6 +443,7 @@ src/main/help/help/topics/FrontEndPlugin/images/VersionedFileIcon.png||GHIDRA|||
|
||||||
src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/images/ViewProjectAccessPanel.png||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/images/ViewProjectAccessPanel.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FrontEndPlugin/images/hijack_file.png||GHIDRA||||END|
|
src/main/help/help/topics/FrontEndPlugin/images/hijack_file.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/FrontEndPlugin/images/start-here_16.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END|
|
src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png||GHIDRA||||END|
|
src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END|
|
src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import ghidra.app.script.ImproperUseException;
|
||||||
import ghidra.framework.data.GhidraFile;
|
import ghidra.framework.data.GhidraFile;
|
||||||
import ghidra.framework.data.GhidraFileData;
|
import ghidra.framework.data.GhidraFileData;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.DataTreeDialog;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.store.FolderItem;
|
import ghidra.framework.store.FolderItem;
|
||||||
import ghidra.framework.store.local.LocalDatabaseItem;
|
import ghidra.framework.store.local.LocalDatabaseItem;
|
||||||
import ghidra.program.model.lang.LanguageDescription;
|
import ghidra.program.model.lang.LanguageDescription;
|
||||||
|
@ -115,8 +115,8 @@ public class FixLangId extends GhidraScript {
|
||||||
if (langId != null) {
|
if (langId != null) {
|
||||||
Msg.warn(this, "Changing language ID from '" + record.getString(0) + "' to '" +
|
Msg.warn(this, "Changing language ID from '" + record.getString(0) + "' to '" +
|
||||||
langId + "' for program: " + df.getName());
|
langId + "' for program: " + df.getName());
|
||||||
desc = DefaultLanguageService.getLanguageService().getLanguageDescription(
|
desc = DefaultLanguageService.getLanguageService()
|
||||||
new LanguageID(langId));
|
.getLanguageDescription(new LanguageID(langId));
|
||||||
long txId = dbh.startTransaction();
|
long txId = dbh.startTransaction();
|
||||||
try {
|
try {
|
||||||
record.setString(0, langId);
|
record.setString(0, langId);
|
||||||
|
@ -139,7 +139,10 @@ public class FixLangId extends GhidraScript {
|
||||||
|
|
||||||
public DomainFile askProgramFile(String title) {
|
public DomainFile askProgramFile(String title) {
|
||||||
final DomainFile[] domainFile = new DomainFile[] { null };
|
final DomainFile[] domainFile = new DomainFile[] { null };
|
||||||
final DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
|
// The file filter employed restricts selection to a program file within the active
|
||||||
|
// project where we have the ability to update file data.
|
||||||
|
final DataTreeDialog dtd =
|
||||||
|
new DataTreeDialog(null, title, OPEN, new DefaultDomainFileFilter(Program.class, true));
|
||||||
dtd.addOkActionListener(e -> {
|
dtd.addOkActionListener(e -> {
|
||||||
dtd.close();
|
dtd.close();
|
||||||
domainFile[0] = dtd.getDomainFile();
|
domainFile[0] = dtd.getDomainFile();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,7 +19,9 @@
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.database.ProgramContentHandler;
|
||||||
import ghidra.util.InvalidNameException;
|
import ghidra.util.InvalidNameException;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -29,8 +30,9 @@ public class RenameProgramsInProjectScript extends GhidraScript {
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Exception {
|
public void run() throws Exception {
|
||||||
|
|
||||||
if ( currentProgram != null ) {
|
if (currentProgram != null) {
|
||||||
popup( "This script should be run from a tool with no open programs" );
|
popup("This script should be run from a tool with no open programs.\n" +
|
||||||
|
"Warning! If using file-links to programs within this project such linkages will break.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,29 +40,34 @@ public class RenameProgramsInProjectScript extends GhidraScript {
|
||||||
Project project = tool.getProject();
|
Project project = tool.getProject();
|
||||||
ProjectData projectData = project.getProjectData();
|
ProjectData projectData = project.getProjectData();
|
||||||
DomainFolder rootFolder = projectData.getRootFolder();
|
DomainFolder rootFolder = projectData.getRootFolder();
|
||||||
recurseProjectFolder( rootFolder );
|
recurseProjectFolder(rootFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recurseProjectFolder( DomainFolder domainFolder ) {
|
private void recurseProjectFolder(DomainFolder domainFolder) throws CancelledException {
|
||||||
DomainFile[] files = domainFolder.getFiles();
|
DomainFile[] files = domainFolder.getFiles();
|
||||||
for ( DomainFile domainFile : files ) {
|
for (DomainFile domainFile : files) {
|
||||||
processDomainFile( domainFile );
|
monitor.checkCancelled();
|
||||||
|
processDomainFile(domainFile);
|
||||||
}
|
}
|
||||||
DomainFolder[] folders = domainFolder.getFolders();
|
DomainFolder[] folders = domainFolder.getFolders();
|
||||||
for ( DomainFolder folder : folders ) {
|
for (DomainFolder folder : folders) {
|
||||||
recurseProjectFolder( folder );
|
monitor.checkCancelled();
|
||||||
|
recurseProjectFolder(folder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDomainFile( DomainFile domainFile ) {
|
private void processDomainFile(DomainFile domainFile) {
|
||||||
|
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domainFile.getContentType())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String oldName = domainFile.getName();
|
String oldName = domainFile.getName();
|
||||||
try {
|
try {
|
||||||
domainFile.setName( oldName + "_renamed" );
|
domainFile.setName(oldName + "_renamed");
|
||||||
}
|
}
|
||||||
catch ( InvalidNameException e ) {
|
catch (InvalidNameException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
catch ( IOException e ) {
|
catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int listCheckouts(DomainFolder folder) throws IOException, CancelledException {
|
private int listCheckouts(DomainFolder folder) throws IOException, CancelledException {
|
||||||
|
// Avoid following folder-links so we don't count the same file more than once.
|
||||||
|
// Link-files will never be in a checked-out state.
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (DomainFile df : folder.getFiles()) {
|
for (DomainFile df : folder.getFiles()) {
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
|
@ -115,8 +117,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int listCheckouts(DomainFile df) throws IOException {
|
private int listCheckouts(DomainFile df) throws IOException {
|
||||||
if (!df.isVersioned()) {
|
if (!df.isVersioned() || df.isLink()) {
|
||||||
return 0;
|
return 0; // ignore non-versioned files and link-files
|
||||||
}
|
}
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (ItemCheckoutStatus checkout : df.getCheckouts()) {
|
for (ItemCheckoutStatus checkout : df.getCheckouts()) {
|
||||||
|
|
|
@ -27,7 +27,6 @@ public class VersionControl_ResetAll extends GhidraScript {
|
||||||
public VersionControl_ResetAll() {
|
public VersionControl_ResetAll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Exception {
|
public void run() throws Exception {
|
||||||
|
|
||||||
|
@ -54,7 +53,8 @@ public class VersionControl_ResetAll extends GhidraScript {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Do not follow folder-links or consider program links. Checking the content type
|
||||||
|
// is the best way to restrict this.
|
||||||
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType()) ||
|
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType()) ||
|
||||||
!file.isVersioned() || file.getLatestVersion() < 2) {
|
!file.isVersioned() || file.getLatestVersion() < 2) {
|
||||||
continue;// skip
|
continue;// skip
|
||||||
|
|
|
@ -61,6 +61,8 @@
|
||||||
|
|
||||||
<LI><A href="#FileIcons">File Icons</A></LI>
|
<LI><A href="#FileIcons">File Icons</A></LI>
|
||||||
|
|
||||||
|
<LI><A href="#GhidraURLFormats">Ghidra URL Formats</A></LI>
|
||||||
|
|
||||||
<LI><A href="#StatusWindow">Console</A></LI>
|
<LI><A href="#StatusWindow">Console</A></LI>
|
||||||
|
|
||||||
<LI><A href="Ghidra_Front_end_Menus.htm#Configure">Configure Project Window</A></LI>
|
<LI><A href="Ghidra_Front_end_Menus.htm#Configure">Configure Project Window</A></LI>
|
||||||
|
@ -93,8 +95,10 @@
|
||||||
<H2><A name="ActiveProjectPanel"></A>Active Project</H2>
|
<H2><A name="ActiveProjectPanel"></A>Active Project</H2>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P>The Active Project view shows your programs and datatype archives in a tree view or a
|
<P>The Active Project view shows the various files associated with the current
|
||||||
table view. The tree view is useful for organizing your files into folders and sub-folders.
|
project which has been open for update. Project files generally consist of programs and
|
||||||
|
datatype archives but may also be related to other Ghidra content.
|
||||||
|
The tree view is useful for organizing your files into folders and sub-folders.
|
||||||
The table view is useful for sorting all your files on some particular attribute such as
|
The table view is useful for sorting all your files on some particular attribute such as
|
||||||
size, processor, or modification date. In either view, you open and perform various
|
size, processor, or modification date. In either view, you open and perform various
|
||||||
actions on program files or datatype archives.</P>
|
actions on program files or datatype archives.</P>
|
||||||
|
@ -105,13 +109,23 @@
|
||||||
</CENTER>
|
</CENTER>
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P>The data tree shows all files in the project orgnanized into folders and sub-folders.
|
<P>The data tree shows all files in the project orgnanized into folders and sub-folders.
|
||||||
<A href="#FileIcons">Icons for files</A>
|
<A href="#FileIcons">Icons for files</A> indicate whether they are under <A href=
|
||||||
indicate whether they are under <A href=
|
|
||||||
"help/topics/VersionControl/project_repository.htm#Versioning">version control</A> and whether
|
"help/topics/VersionControl/project_repository.htm#Versioning">version control</A> and whether
|
||||||
you have the file <A href=
|
you have the file <A href=
|
||||||
"help/topics/VersionControl/project_repository.htm#SampleCheckOutIcon">checked out</A>.
|
"help/topics/VersionControl/project_repository.htm#SampleCheckOutIcon">checked out</A>.
|
||||||
Open this view by activating the "Tree View" tab.</P>
|
In addition, unique icons are used to reflect content-type and if it corresponds to
|
||||||
|
a link-file referring to another file or folder (see <A href="#Paste_Link">creating links</A>).
|
||||||
|
Open this view by activating the project window "Tree View" tab.</P>
|
||||||
|
|
||||||
|
<P><IMG src="help/shared/tip.png" border="0">Although Ghidra allows a folder and file within
|
||||||
|
the same parent folder to have the same name, it is recommended this be avoided if possible.
|
||||||
|
Allowing both a folder and file to have the same pathname can result in ambiguous path problems
|
||||||
|
when using link files and/or Ghidra URLs where only a path is used to identify either a project
|
||||||
|
resource.
|
||||||
|
</P>
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<P> </P>
|
<P> </P>
|
||||||
<H3>Tree Only Actions</H3>
|
<H3>Tree Only Actions</H3>
|
||||||
|
|
||||||
|
@ -124,7 +138,7 @@
|
||||||
<P>To create a new folder,</P>
|
<P>To create a new folder,</P>
|
||||||
|
|
||||||
<OL>
|
<OL>
|
||||||
<LI>Select a folder that you own. </LI>
|
<LI>Select a folder which should contain the new folder.</LI>
|
||||||
|
|
||||||
<LI>Right mouse click and choose the <I>New Folder</I> option.</LI>
|
<LI>Right mouse click and choose the <I>New Folder</I> option.</LI>
|
||||||
|
|
||||||
|
@ -133,8 +147,6 @@
|
||||||
editing.</LI>
|
editing.</LI>
|
||||||
</OL>
|
</OL>
|
||||||
|
|
||||||
<P><IMG src="help/shared/note.png" border="0"> You cannot create
|
|
||||||
a sub-folder of a folder that you do not own.</P>
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H4><A name="Copy"></A><A name="Paste"></A>Copy Folders and Files</H4>
|
<H4><A name="Copy"></A><A name="Paste"></A>Copy Folders and Files</H4>
|
||||||
|
@ -160,6 +172,46 @@
|
||||||
</OL>
|
</OL>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<H4><A name="Paste_Link"></A><A name="Paste_Relative_Link"></A>Paste Copied Folder or File as a Link</H4>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>A Link may be created within the active project to a file or folder within the
|
||||||
|
same project (internal) or to a viewed project/repository (external).
|
||||||
|
Internal links may be defined using either a relative path or an absolute path. Once
|
||||||
|
a link is created its stored path will not change. The link will need to be replaced
|
||||||
|
should the referenced path need to be changed. In addition, file-links are specific
|
||||||
|
to the content-type of the referenced file at the time of link creation (e.g.,
|
||||||
|
ProgramLink).
|
||||||
|
</P>
|
||||||
|
<P>To create a Link use the following steps from the source project data tree:</P>
|
||||||
|
<OL>
|
||||||
|
<LI>Select a single file or folder, right mouse click and choose the <I>Copy</I> option.</LI>
|
||||||
|
|
||||||
|
<LI>Select a destination folder within the active project data tree.</LI>
|
||||||
|
|
||||||
|
<LI>Right mouse click and choose the <I>Paste as Link</I> or <I>Paste as Relative-Link</I>
|
||||||
|
option.</LI>
|
||||||
|
</OL>
|
||||||
|
|
||||||
|
<P>See <A href="#Create_File_Links">Create Linked Folder or File</A> for more information
|
||||||
|
about links and creating external links.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<P>An internal link in the project tree may indicate a "broken" status for
|
||||||
|
various reasons, including:</P>
|
||||||
|
<ul>
|
||||||
|
<li>The referenced file or folder does not exist,</li>
|
||||||
|
<li>the content-type at the referenced location does not match the link type, or</li>
|
||||||
|
<li>a folder-link results in a circular path reference.</li>
|
||||||
|
</ul>
|
||||||
|
<P>A broken link will have an icon which conveys its type but with a jagged red line
|
||||||
|
through it and a tooltip which conveys the issue detected.</P>
|
||||||
|
|
||||||
|
<P><IMG src="help/shared/note.png" border="0">External links will never show a broken
|
||||||
|
link state since they are not evaluated for such conditions.</P>
|
||||||
|
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H4><A name="Cut"></A>Move Folders and Files</H4>
|
<H4><A name="Cut"></A>Move Folders and Files</H4>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
|
@ -182,7 +234,7 @@
|
||||||
</OL>
|
</OL>
|
||||||
|
|
||||||
<P><IMG src="help/shared/note.png" border="0">You cannot move a
|
<P><IMG src="help/shared/note.png" border="0">You cannot move a
|
||||||
file that is in use.</P>
|
file that is in use or a folder that contains a file that is in use.</P>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H4>Drag/Drop for Copy</H4>
|
<H4>Drag/Drop for Copy</H4>
|
||||||
|
@ -217,6 +269,9 @@
|
||||||
|
|
||||||
<LI>Release the mouse button when you get a valid drop target.</LI>
|
<LI>Release the mouse button when you get a valid drop target.</LI>
|
||||||
</OL>
|
</OL>
|
||||||
|
|
||||||
|
<P><IMG src="help/shared/note.png" border="0">You cannot move a
|
||||||
|
file that is in use or a folder that contains a file that is in use.</P>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<P><IMG src="help/shared/note.png" border="0"> If a folder or file
|
<P><IMG src="help/shared/note.png" border="0"> If a folder or file
|
||||||
|
@ -242,6 +297,24 @@
|
||||||
</OL>
|
</OL>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<H4><A name="Follow_Link"></A>Follow Link</H4>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>Select the internal or external folder or file referenced by a selected link-file.
|
||||||
|
While internal folders may be expanded directly from a folder-link, following a link
|
||||||
|
to the actual referenced location may be useful at times.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<OL>
|
||||||
|
<LI>
|
||||||
|
Select a file-link or folder-link, right mouse click and choose the <I>Follow Link</I>
|
||||||
|
option. The referenced file or folder will be selected if possible. If associated
|
||||||
|
with an external project or repository the selection will occur in a READ-ONLY
|
||||||
|
project view once opened.</LI>
|
||||||
|
</OL>
|
||||||
|
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
|
||||||
<P> </P>
|
<P> </P>
|
||||||
|
|
||||||
|
@ -421,9 +494,9 @@
|
||||||
<TD style="vertical-align: top; width: 10px;">-<BR>
|
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||||
</TD>
|
</TD>
|
||||||
|
|
||||||
<TD style="vertical-align: top;">A <A href=
|
<TD style="vertical-align: top;"><A href=
|
||||||
"help/topics/Program/Ghidra_Programs.htm"><SPAN style=
|
"help/topics/Program/Ghidra_Programs.htm"><SPAN style=
|
||||||
"font-weight: bold;">program</SPAN></A><BR>
|
"font-weight: bold;">Program</SPAN></A><BR>
|
||||||
</TD>
|
</TD>
|
||||||
</TR>
|
</TR>
|
||||||
|
|
||||||
|
@ -434,12 +507,34 @@
|
||||||
<TD style="vertical-align: top; width: 10px;">-<BR>
|
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||||
</TD>
|
</TD>
|
||||||
|
|
||||||
<TD style="vertical-align: top;">A <A href=
|
<TD style="vertical-align: top;"><A href=
|
||||||
"help/topics/DataTypeManagerPlugin/data_type_manager_description.htm#ProjectDataTypeArchive"><SPAN
|
"help/topics/DataTypeManagerPlugin/data_type_manager_description.htm#ProjectDataTypeArchive"><SPAN
|
||||||
style="font-weight: bold;">project data type archive</SPAN></A> (a data type file
|
style="font-weight: bold;">Data Type Archive</SPAN></A> (a data type file
|
||||||
stored in the project)<BR>
|
stored in the project)<BR>
|
||||||
</TD>
|
</TD>
|
||||||
</TR>
|
</TR>
|
||||||
|
|
||||||
|
<TR>
|
||||||
|
<TD style="vertical-align: top; width: 20px;"><IMG alt="" src=
|
||||||
|
"images/video-x-generic16.png"></TD>
|
||||||
|
|
||||||
|
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||||
|
</TD>
|
||||||
|
|
||||||
|
<TD style="vertical-align: top;">Debugger Trace Data<BR>
|
||||||
|
</TD>
|
||||||
|
</TR>
|
||||||
|
|
||||||
|
<TR>
|
||||||
|
<TD style="vertical-align: top; width: 20px;"><IMG alt="" src=
|
||||||
|
"images/start-here_16.png"></TD>
|
||||||
|
|
||||||
|
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||||
|
</TD>
|
||||||
|
|
||||||
|
<TD style="vertical-align: top;">Version Tracking Session Data<BR>
|
||||||
|
</TD>
|
||||||
|
</TR>
|
||||||
</TBODY>
|
</TBODY>
|
||||||
</TABLE>
|
</TABLE>
|
||||||
|
|
||||||
|
@ -522,6 +617,61 @@
|
||||||
other users.</TD>
|
other users.</TD>
|
||||||
</TR>
|
</TR>
|
||||||
|
|
||||||
|
<TR>
|
||||||
|
<TD width="25%">File Link </TD>
|
||||||
|
|
||||||
|
<TD width="20%"><IMG src="images/AbsoluteFileLinkIcon.png" border="0"></TD>
|
||||||
|
|
||||||
|
<TD width="40%"><A name="FileLink"></A>A file link named "Example" which refers to
|
||||||
|
a Program at <I>/data/example</I>. File links may reference another file using either an
|
||||||
|
1) absolute file path within the same project, 2) a relative file path within
|
||||||
|
the same project, 3) a shared repository Ghidra URL, or 4) a local project Ghidra URL.
|
||||||
|
See <A href="#GhidraURLFormats">Ghidra URL formats</A> below.
|
||||||
|
A file link may appear with various icon states which correspond to version control.
|
||||||
|
File links only support a single version and may not be modified.
|
||||||
|
</TD>
|
||||||
|
</TR>
|
||||||
|
|
||||||
|
<TR>
|
||||||
|
<TD width="25%">File Link (Broken) </TD>
|
||||||
|
|
||||||
|
<TD width="20%"><IMG src="images/AbsoluteBrokenFileLinkIcon.png" border="0"></TD>
|
||||||
|
|
||||||
|
<TD width="40%"><A name="BrokenFileLink"></A>A file link named "Example" which refers to
|
||||||
|
a Program at <I>/data/example</I> and is in a "Broken" state. Hovering the mouse
|
||||||
|
on this node will display a tooltip which indicates the reason for the broken state.
|
||||||
|
External file links will never show a broken link state since they are not evaluated for such conditions.
|
||||||
|
</TD>
|
||||||
|
</TR>
|
||||||
|
|
||||||
|
<TR>
|
||||||
|
<TD width="25%">Folder Link </TD>
|
||||||
|
|
||||||
|
<TD width="20%"><IMG src="images/AbsoluteFolderLinkIcon.png" border="0"></TD>
|
||||||
|
|
||||||
|
<TD width="40%"><A name="FolderLink"></A>A folder link named "Example" which refers
|
||||||
|
to a folder at <I>/data/example</I>. Folder links may reference another folder using either an
|
||||||
|
1) absolute file path within the same project, 2) a relative file path within
|
||||||
|
the same project, 3) a shared repository Ghidra URL, or 4) a local project Ghidra URL.
|
||||||
|
See <A href="#GhidraURLFormats">Ghidra URL formats</A> below.
|
||||||
|
Since a folder link is stored as a file, it may appear with various icon states which
|
||||||
|
correspond to version control. Folder links only support a single version and may not
|
||||||
|
be modified.
|
||||||
|
</TD>
|
||||||
|
</TR>
|
||||||
|
|
||||||
|
<TR>
|
||||||
|
<TD width="25%">Folder Link (Broken) </TD>
|
||||||
|
|
||||||
|
<TD width="20%"><IMG src="images/AbsoluteBrokenFolderLinkIcon.png" border="0"></TD>
|
||||||
|
|
||||||
|
<TD width="40%"><A name="BrokenFolderLink"></A>A folder link named "Example" which refers to
|
||||||
|
a folder at <I>/data/example</I> and is in a "Broken" state. Hovering the mouse
|
||||||
|
on this node will display a tooltip which indicates the reason for the broken state.
|
||||||
|
External folder links will never show a broken link state since they are not evaluated for such conditions.
|
||||||
|
</TD>
|
||||||
|
</TR>
|
||||||
|
|
||||||
<TR>
|
<TR>
|
||||||
<TD width="25%">Hijacked File</TD>
|
<TD width="25%">Hijacked File</TD>
|
||||||
|
|
||||||
|
@ -545,9 +695,44 @@
|
||||||
</TABLE>
|
</TABLE>
|
||||||
</CENTER>
|
</CENTER>
|
||||||
</DIV>
|
</DIV>
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<H3> </H3>
|
<H3><A name="GhidraURLFormats"></A>Ghidra URL Formats</H3>
|
||||||
|
|
||||||
|
<P>The format of a remote <EM>Ghidra Server URL</EM> is distinctly different from a
|
||||||
|
<EM>Local Ghidra Project URL</EM>. These URLs have the following formats:</P>
|
||||||
|
|
||||||
|
<P><STRONG>Remote Ghidra Server Repository</STRONG><BR>
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<TABLE border="0" class="simplelist">
|
||||||
|
<TR>
|
||||||
|
<TD><CODE>ghidra://<hostname>[:<port>]/<repository_name>[/<folder_or_file_path>]</CODE></TD>
|
||||||
|
</TR>
|
||||||
|
</TABLE>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<P>If the default Ghidra Server port (13100) is in use it is not specified by the URL.
|
||||||
|
The <EM>hostname</EM> may specify either a Fully Qualified Domain Name (FQDN, e.g.,
|
||||||
|
<EM>host.abc.com</EM>) or IP v4 Address (e.g., <EM>1.2.3.4</EM>).</P>
|
||||||
|
|
||||||
|
<P><STRONG>Local Ghidra Project</STRONG><BR>
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<TABLE border="0" class="simplelist">
|
||||||
|
<TR>
|
||||||
|
<TD><CODE>ghidra:[/<directory_path>]/<project_name>[?/<folder_or_file_path>]</CODE></TD>
|
||||||
|
</TR>
|
||||||
|
</TABLE>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<P>For local project URLs, the absolute directory path containing the project
|
||||||
|
<EM>*.gpr</EM> locator file is specified with the project name but excludes any <EM>.gpr/.rep</EM> suffix.
|
||||||
|
The folder or file path within the project is conveyed with a URL query so the '?' is required.</P>
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H2><A name="ReadOnlyProjectDataPanel"></A>Read-Only Project Data</H2>
|
<H2><A name="ReadOnlyProjectDataPanel"></A>Read-Only Project Data</H2>
|
||||||
|
@ -697,47 +882,54 @@
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P>This feature allows you to create a folder or file link in your active project to a
|
<P>This feature allows you to create a folder or file link in your active project to a
|
||||||
corresponding folder or file within a read-only viewed project.
|
corresponding folder or file within your project or to a read-only viewed project.
|
||||||
This is done using a Ghidra URL which references the
|
External links are established using a Ghidra URL which references a
|
||||||
file in its local or remote storage location. If the viewed project corresponds to a
|
file or folder in its local or remote storage location. An external Ghidra URL will
|
||||||
viewed repository a remote URL will be used, while other cases will refer to the
|
be used if a link refers to a viewed project or repository. It is possible for internal links to
|
||||||
locally viewed project. It is possible for links to become broken if the referenced
|
become broken if the referenced file or folder location has changed (e.g., no longers exists
|
||||||
repository, local project or file location are changed.</P>
|
or has the wrong content type). External links may become invalid for various reasons
|
||||||
|
but will not convey an issue until the link is used. The broken link icon does not apply
|
||||||
|
to external link files.
|
||||||
|
</P>
|
||||||
|
<P>To create an external folder or file link the following steps may be used:</P>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Select a single folder or file from a viewed READ-ONLY Project Data tree.</li>
|
<li>Select a single folder or file from a viewed READ-ONLY Project Data tree.</li>
|
||||||
<li>Right mouse click on the selected tree node and choose the <I>Copy</I> option.</li>
|
<li>Right mouse click on the selected tree node and choose the <I>Copy</I> option.</li>
|
||||||
<li>Select a destination folder in the active project tree.</li>
|
<li>Select a destination folder in the active project data tree.</li>
|
||||||
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.
|
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.</li>
|
||||||
<P><IMG src="help/shared/note.png" border="0">Currently, linked-file types are
|
|
||||||
currently limited to <I>Program</I> and <I>Data Type Archive</I> files
|
|
||||||
only. The <I>Past as Link</I> menu item will be disabled for
|
|
||||||
unsupported file content types or for other unsupported situations such as internal
|
|
||||||
linking within the same project.</P>
|
|
||||||
</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
<P>A linked-file may be opened in a tool via the project window in the same fashion that
|
<P>It is important to note that the resulting link is always stored as a file within the
|
||||||
|
project. With the exception of external links to local project content, a link may be
|
||||||
|
added to version control so that it may be shared. Once added to version control it cannot
|
||||||
|
be checked-out, since they are immutable, however they can still be deleted.</P>
|
||||||
|
<P>A file-link may be opened in a tool via the project window in the same fashion that
|
||||||
a normal file is opened (e.g., double-left-mouse-click or drag-n-drop onto a tool box icon).
|
a normal file is opened (e.g., double-left-mouse-click or drag-n-drop onto a tool box icon).
|
||||||
Such a project file may also be opened within a Tool using its <B>File->Open...</B> action
|
Such a project file may also be opened within a Tool using its <B>File->Open...</B> action
|
||||||
and selected from the resulting project file selection dilaog.
|
and selected from the resulting project file selection dilaog.
|
||||||
Clicking on a linked-folder in the active project window will open that location in a
|
Clicking on an external folder-link in the active project window will open that location in a
|
||||||
<B>READ-ONLY Project Data</B> tree. The user may be prompted for a shared repository
|
<B>READ-ONLY Project Data</B> tree. The user may be prompted for a shared repository
|
||||||
connection password when accessing a linked folder or file.</P>
|
connection password when accessing an external folder or file link.</P>
|
||||||
<P>Within a project file chooser dialog a linked-folder may be expanded in a similar fashion
|
<P>Within a project file chooser dialog a folder-link may be expanded in a similar fashion
|
||||||
to local folders provided any neccessary repository connection can be completed.</P>
|
to local folders provided any neccessary repository connection can be completed.</P>
|
||||||
<P><IMG src="help/shared/note.png" border="0"><B>Add to Version Control...</B> is supported
|
<P><IMG src="help/shared/note.png" border="0">Currently, external file-links only provide access
|
||||||
for repository folder and file links only and will be disabled for links to a
|
to the latest file version and do not facilitate access to older file versions. An external
|
||||||
local project.</P>
|
folder-link will allow access to file versions contained within such a folder.
|
||||||
<P><IMG src="help/shared/note.png" border="0">Currently, linked-files only provide access
|
</P>
|
||||||
to the latest file version and do not facilitate access to older file versions.</P>
|
<P><IMG src="help/shared/note.png" border="0">Some file chooser use cases, including the
|
||||||
|
<I>GhidraScript</I> API, are restricted to selecting files and folders within the active
|
||||||
|
project only and will hide all external links.
|
||||||
|
</P>
|
||||||
<P>The project window below shows a Program file-link "Program1" which is linked to the
|
<P>The project window below shows a Program file-link "Program1" which is linked to the
|
||||||
same file in the viewed project. Hovering the mouse over a linked-file will show the URL
|
same file in the viewed project.</P>
|
||||||
of the linked file or folder. The chain-link icon decoration indicates such a linked
|
|
||||||
file or folder.</P>
|
|
||||||
|
|
||||||
<CENTER>
|
<CENTER>
|
||||||
<IMG src= "images/LinkOtherProject.png" border="0">
|
<IMG src= "images/LinkOtherProject.png" border="0">
|
||||||
</CENTER>
|
</CENTER>
|
||||||
|
|
||||||
|
<P>A folder or file link will show its referenced location with either
|
||||||
|
same file in the viewed project.</P>
|
||||||
|
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1,003 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 658 B |
|
@ -133,8 +133,9 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
|
||||||
OPTION_DESCRIPTION_GDT_FILEPATH,
|
OPTION_DESCRIPTION_GDT_FILEPATH,
|
||||||
() -> new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER));
|
() -> new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER));
|
||||||
options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null,
|
options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null,
|
||||||
OPTION_DESCRIPTION_PROJECT_PATH, () -> new ProjectPathChooserEditor(
|
OPTION_DESCRIPTION_PROJECT_PATH,
|
||||||
"Choose Data Type Archive", DATATYPEARCHIVE_PROJECT_FILTER));
|
() -> new ProjectPathChooserEditor("Choose Data Type Archive",
|
||||||
|
new DefaultDomainFileFilter(DataTypeArchive.class, false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -289,6 +290,4 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
|
||||||
.collect(Collectors.toMap(f -> f.getName(), f -> f));
|
.collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final DomainFileFilter DATATYPEARCHIVE_PROJECT_FILTER =
|
|
||||||
df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,6 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
|
||||||
private String title;
|
private String title;
|
||||||
private DomainFileFilter filter;
|
private DomainFileFilter filter;
|
||||||
|
|
||||||
public ProjectPathChooserEditor() {
|
|
||||||
this(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProjectPathChooserEditor(String title, DomainFileFilter filter) {
|
public ProjectPathChooserEditor(String title, DomainFileFilter filter) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
|
@ -127,8 +123,7 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
|
||||||
|
|
||||||
private void displayFileChooser() {
|
private void displayFileChooser() {
|
||||||
AtomicReference<String> result = new AtomicReference<>();
|
AtomicReference<String> result = new AtomicReference<>();
|
||||||
DataTreeDialog dataTreeDialog =
|
DataTreeDialog dataTreeDialog = new DataTreeDialog(this, title, OPEN, filter);
|
||||||
new DataTreeDialog(this, title, OPEN, filter);
|
|
||||||
dataTreeDialog.addOkActionListener(e -> {
|
dataTreeDialog.addOkActionListener(e -> {
|
||||||
dataTreeDialog.close();
|
dataTreeDialog.close();
|
||||||
DomainFile df = dataTreeDialog.getDomainFile();
|
DomainFile df = dataTreeDialog.getDomainFile();
|
||||||
|
|
|
@ -92,8 +92,7 @@ class OpenDomainFileTask extends Task {
|
||||||
|
|
||||||
private boolean isFileOpen() {
|
private boolean isFileOpen() {
|
||||||
List<Archive> dtArchiveList = dtmHandler.getAllArchives();
|
List<Archive> dtArchiveList = dtmHandler.getAllArchives();
|
||||||
for (int i = 0; i < dtArchiveList.size(); i++) {
|
for (Archive archive : dtArchiveList) {
|
||||||
Archive archive = dtArchiveList.get(i);
|
|
||||||
if (archive instanceof ProjectArchive) {
|
if (archive instanceof ProjectArchive) {
|
||||||
ProjectArchive projectArchive = (ProjectArchive) archive;
|
ProjectArchive projectArchive = (ProjectArchive) archive;
|
||||||
DomainFile archiveDomainFile = projectArchive.getDomainFile();
|
DomainFile archiveDomainFile = projectArchive.getDomainFile();
|
||||||
|
@ -156,7 +155,7 @@ class OpenDomainFileTask extends Task {
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
VersionExceptionHandler.showVersionError(tool.getToolFrame(), domainFile.getName(),
|
VersionExceptionHandler.showVersionError(tool.getToolFrame(), domainFile.getName(),
|
||||||
contentType, "Open", e);
|
contentType, "Open", false, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +178,7 @@ class OpenDomainFileTask extends Task {
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
|
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
|
||||||
"Open", e);
|
"Open", false, e);
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
// we don't care, the task has been canceled
|
// we don't care, the task has been canceled
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class DataTypeManagerHandler {
|
||||||
private Map<UniversalID, InvalidFileArchive> invalidArchives = new HashMap<>();
|
private Map<UniversalID, InvalidFileArchive> invalidArchives = new HashMap<>();
|
||||||
|
|
||||||
private boolean treeDialogCancelled = false;
|
private boolean treeDialogCancelled = false;
|
||||||
private DomainFileFilter createArchiveFileFilter;
|
private DomainFileFilter archiveFileFilter;
|
||||||
|
|
||||||
private DataTypeIndexer dataTypeIndexer;
|
private DataTypeIndexer dataTypeIndexer;
|
||||||
private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>();
|
private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>();
|
||||||
|
@ -107,18 +107,7 @@ public class DataTypeManagerHandler {
|
||||||
dataTypeIndexer.addDataTypeManager(builtInDataTypesManager);
|
dataTypeIndexer.addDataTypeManager(builtInDataTypesManager);
|
||||||
openArchives.add(new BuiltInArchive(this, builtInDataTypesManager));
|
openArchives.add(new BuiltInArchive(this, builtInDataTypesManager));
|
||||||
|
|
||||||
createArchiveFileFilter = new DomainFileFilter() {
|
archiveFileFilter = new DefaultDomainFileFilter(DataTypeArchive.class, true);
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean accept(DomainFile df) {
|
|
||||||
return DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean followLinkedFolders() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
folderListener = new MyFolderListener();
|
folderListener = new MyFolderListener();
|
||||||
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
|
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
|
||||||
|
@ -1454,7 +1443,7 @@ public class DataTypeManagerHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataTreeDialog getSaveDialog() {
|
private DataTreeDialog getSaveDialog() {
|
||||||
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, createArchiveFileFilter);
|
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, archiveFileFilter);
|
||||||
|
|
||||||
ActionListener listener = event -> {
|
ActionListener listener = event -> {
|
||||||
DomainFolder folder = dialog.getDomainFolder();
|
DomainFolder folder = dialog.getDomainFolder();
|
||||||
|
@ -1486,7 +1475,7 @@ public class DataTypeManagerHandler {
|
||||||
private CreateDataTypeArchiveDataTreeDialog getCreateDialog() {
|
private CreateDataTypeArchiveDataTreeDialog getCreateDialog() {
|
||||||
|
|
||||||
CreateDataTypeArchiveDataTreeDialog dialog = new CreateDataTypeArchiveDataTreeDialog(null,
|
CreateDataTypeArchiveDataTreeDialog dialog = new CreateDataTypeArchiveDataTreeDialog(null,
|
||||||
"Create", CREATE, createArchiveFileFilter);
|
"Create", CREATE, archiveFileFilter);
|
||||||
|
|
||||||
ActionListener listener = event -> {
|
ActionListener listener = event -> {
|
||||||
DomainFolder folder = dialog.getDomainFolder();
|
DomainFolder folder = dialog.getDomainFolder();
|
||||||
|
@ -1726,7 +1715,7 @@ public class DataTypeManagerHandler {
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
VersionExceptionHandler.showVersionError(null, newDomainFile.getName(), contentType,
|
VersionExceptionHandler.showVersionError(null, newDomainFile.getName(), contentType,
|
||||||
"Re-open", e);
|
"Re-open", false, e);
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
throw new AssertException(e);
|
throw new AssertException(e);
|
||||||
|
@ -1766,7 +1755,7 @@ public class DataTypeManagerHandler {
|
||||||
Throwable cause = t.getCause();
|
Throwable cause = t.getCause();
|
||||||
if (cause instanceof VersionException) {
|
if (cause instanceof VersionException) {
|
||||||
VersionExceptionHandler.showVersionError(null, archiveFile.getName(), "Archive",
|
VersionExceptionHandler.showVersionError(null, archiveFile.getName(), "Archive",
|
||||||
"open", (VersionException) cause);
|
"open", false, (VersionException) cause);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Msg.showError(plugin, plugin.getProvider().getComponent(), "Open Archive Failed",
|
Msg.showError(plugin, plugin.getProvider().getComponent(), "Open Archive Failed",
|
||||||
|
|
|
@ -78,6 +78,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
|
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
|
||||||
private JTextField filePathTextField;
|
private JTextField filePathTextField;
|
||||||
private JButton fileChooserButton;
|
private JButton fileChooserButton;
|
||||||
|
private List<Exporter> applicableExporters;
|
||||||
private GhidraComboBox<Exporter> comboBox;
|
private GhidraComboBox<Exporter> comboBox;
|
||||||
private final DomainFile domainFile;
|
private final DomainFile domainFile;
|
||||||
private boolean domainObjectWasSupplied;
|
private boolean domainObjectWasSupplied;
|
||||||
|
@ -86,39 +87,82 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
|
|
||||||
private JLabel selectionOnlyLabel;
|
private JLabel selectionOnlyLabel;
|
||||||
|
private boolean showNoExporterErrorIfNeeded = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new ExporterDialog for exporting an entire program.
|
* Show a new ExporterDialog for exporting an entire program.
|
||||||
|
* The method {@link #hasNoApplicableExporter()} should be checked before showing the
|
||||||
|
* dilaog. If no exporters are available a popup error will be displayed and the exporter
|
||||||
|
* dialog will not be shown.
|
||||||
*
|
*
|
||||||
* @param tool the tool that launched this dialog.
|
* @param tool the tool that launched this dialog.
|
||||||
* @param domainFile the program to export
|
* @param domainFile the program to export
|
||||||
*/
|
*/
|
||||||
public ExporterDialog(PluginTool tool, DomainFile domainFile) {
|
public static void show(PluginTool tool, DomainFile domainFile) {
|
||||||
this(tool, domainFile, null, null);
|
showExporterDialog(tool, domainFile, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new ExporterDialog for exporting a program, optionally only exported a
|
* Construct a new ExporterDialog for exporting a program, optionally only exported a
|
||||||
* selected region.
|
* selected region. The method {@link #hasNoApplicableExporter()} should be checked before
|
||||||
|
* showing the dilaog. If no exporters are available a popup error will be displayed and the
|
||||||
|
* exporter dialog will not be shown.
|
||||||
|
* The {@link #close()} method must always be invoked on the dialog instance even if it
|
||||||
|
* is never shown to ensure any {@link DomainObject} instance held is properly released.
|
||||||
*
|
*
|
||||||
* @param tool the tool that launched this dialog.
|
* @param tool the tool that launched this dialog.
|
||||||
* @param domainFile the program file to export. (may be proxy)
|
* @param domainFile the program file to export. (may be proxy)
|
||||||
* @param domainObject the program to export if already open, otherwise null.
|
* @param domainObject the program to export if already open, otherwise null.
|
||||||
* @param selection the current program selection (ignored for FrontEnd Tool).
|
* @param selection the current program selection (ignored for FrontEnd Tool).
|
||||||
*/
|
*/
|
||||||
public ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
|
public static void showExporterDialog(PluginTool tool, DomainFile domainFile,
|
||||||
|
DomainObject domainObject, ProgramSelection selection) {
|
||||||
|
ExporterDialog dialog = new ExporterDialog(tool, domainFile, domainObject, selection);
|
||||||
|
if (dialog.hasNoApplicableExporter()) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tool.showDialog(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new modal ExporterDialog for exporting a program, optionally only exported a
|
||||||
|
* selected region. The method {@link #hasNoApplicableExporter()} should be checked before
|
||||||
|
* showing the dilaog. If no exporters are available a popup error will be displayed.
|
||||||
|
* The {@link #close()} method must always be invoked on the dialog instance even if it
|
||||||
|
* is never shown to ensure any {@link DomainObject} instance held is properly released.
|
||||||
|
*
|
||||||
|
* @param tool the tool that launched this dialog.
|
||||||
|
* @param domainFile the program file to export. (may be proxy)
|
||||||
|
* @param domainObject the program to export if already open, otherwise null.
|
||||||
|
* @param selection the current program selection (ignored for FrontEnd Tool).
|
||||||
|
*/
|
||||||
|
private ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
|
||||||
ProgramSelection selection) {
|
ProgramSelection selection) {
|
||||||
super("Export " + domainFile.getName());
|
super("Export " + domainFile.getName());
|
||||||
|
|
||||||
|
if (!Swing.isSwingThread()) {
|
||||||
|
throw new RuntimeException("ExporterDialog must be instantiated within Swing thread");
|
||||||
|
}
|
||||||
|
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.domainFile = domainFile;
|
this.domainFile = domainFile;
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.currentSelection = selection;
|
this.currentSelection = selection;
|
||||||
|
|
||||||
if (domainObject != null) {
|
if (domainObject != null) {
|
||||||
|
applicableExporters = getApplicableExporters(false);
|
||||||
domainObjectWasSupplied = true;
|
domainObjectWasSupplied = true;
|
||||||
domainObject.addConsumer(this);
|
domainObject.addConsumer(this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
domainObject = getDomainObjectIfNeeded(TaskMonitor.DUMMY);
|
applicableExporters = getApplicableExporters(true);
|
||||||
|
List<Exporter> applicableDomainFileExporters = getApplicableExporters(false);
|
||||||
|
|
||||||
|
domainObject = getDomainObjectIfNeeded(!applicableDomainFileExporters.isEmpty());
|
||||||
|
|
||||||
|
applicableExporters = getApplicableExporters(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
addWorkPanel(buildWorkPanel());
|
addWorkPanel(buildWorkPanel());
|
||||||
|
@ -133,6 +177,11 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
// need to initialize a few things
|
// need to initialize a few things
|
||||||
selectedFormatChanged();
|
selectedFormatChanged();
|
||||||
validate();
|
validate();
|
||||||
|
|
||||||
|
if (showNoExporterErrorIfNeeded && hasNoApplicableExporter()) {
|
||||||
|
Msg.showError(this, tool.getToolFrame(), "Unable to Export",
|
||||||
|
"No available exporters for content type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -305,10 +354,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
|
|
||||||
private Component buildFormatChooser() {
|
private Component buildFormatChooser() {
|
||||||
|
|
||||||
List<Exporter> exporters = getApplicableExporters(false);
|
comboBox = new GhidraComboBox<>(applicableExporters);
|
||||||
comboBox = new GhidraComboBox<>(exporters);
|
|
||||||
|
|
||||||
Exporter defaultExporter = getDefaultExporter(exporters);
|
Exporter defaultExporter = getDefaultExporter(applicableExporters);
|
||||||
if (defaultExporter != null) {
|
if (defaultExporter != null) {
|
||||||
comboBox.setSelectedItem(defaultExporter);
|
comboBox.setSelectedItem(defaultExporter);
|
||||||
}
|
}
|
||||||
|
@ -319,8 +367,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This list generation will be based upon the open domainObject if available, otherwise
|
* This list generation will be based upon the open domainObject if available, otherwise
|
||||||
* the domainFile's content class will be used.
|
* the domainFile's content class will be used. The {@code applicableExporters} variable
|
||||||
* @return list of exporters able to handle content
|
* is set to the applicable list of exporters.
|
||||||
*/
|
*/
|
||||||
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
|
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
|
||||||
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
|
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
|
||||||
|
@ -330,16 +378,14 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
|
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
|
||||||
if (exporter.canExportDomainFile(domainFile)) {
|
if (domainObject != null) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (domainObject == null) {
|
|
||||||
return preliminaryCheck
|
|
||||||
? exporter.canExportDomainObject(domainFile.getDomainObjectClass())
|
|
||||||
: false;
|
|
||||||
}
|
|
||||||
return exporter.canExportDomainObject(domainObject);
|
return exporter.canExportDomainObject(domainObject);
|
||||||
}
|
}
|
||||||
|
if (preliminaryCheck) {
|
||||||
|
return exporter.canExportDomainObject(domainFile.getDomainObjectClass());
|
||||||
|
}
|
||||||
|
return exporter.canExportDomainFile(domainFile);
|
||||||
|
}
|
||||||
|
|
||||||
private Exporter getDefaultExporter(List<Exporter> list) {
|
private Exporter getDefaultExporter(List<Exporter> list) {
|
||||||
|
|
||||||
|
@ -410,6 +456,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
setOkEnabled(true);
|
setOkEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasNoApplicableExporter() {
|
||||||
|
return applicableExporters.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasOptions() {
|
private boolean hasOptions() {
|
||||||
return options != null && !options.isEmpty();
|
return options != null && !options.isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -450,7 +500,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DomainObject getDomainObjectIfNeeded(TaskMonitor taskMonitor) {
|
private DomainObject getDomainObjectIfNeeded(boolean exportPossibleWithoutOpening) {
|
||||||
if (domainObject != null) {
|
if (domainObject != null) {
|
||||||
return domainObject;
|
return domainObject;
|
||||||
}
|
}
|
||||||
|
@ -459,7 +509,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
// direct domain file export. This avoids potential upgrade issues and preserves
|
// direct domain file export. This avoids potential upgrade issues and preserves
|
||||||
// database in its current state for those exporters.
|
// database in its current state for those exporters.
|
||||||
boolean doOpen = false;
|
boolean doOpen = false;
|
||||||
for (Exporter exporter : getApplicableExporters(true)) {
|
for (Exporter exporter : applicableExporters) {
|
||||||
if (!exporter.canExportDomainFile(domainFile)) {
|
if (!exporter.canExportDomainFile(domainFile)) {
|
||||||
doOpen = true;
|
doOpen = true;
|
||||||
break;
|
break;
|
||||||
|
@ -469,35 +519,28 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SystemUtilities.isEventDispatchThread()) {
|
|
||||||
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
|
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
|
||||||
monitor -> doOpenFile(monitor));
|
monitor -> doOpenFile(exportPossibleWithoutOpening, monitor));
|
||||||
}
|
|
||||||
else {
|
|
||||||
doOpenFile(taskMonitor);
|
|
||||||
}
|
|
||||||
return domainObject;
|
return domainObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doOpenFile(TaskMonitor monitor) {
|
private void doOpenFile(boolean exportPossibleWithoutOpening, TaskMonitor monitor) {
|
||||||
|
showNoExporterErrorIfNeeded = false;
|
||||||
|
String linkedPrefix = domainFile.isLink() ? "linked-" : "";
|
||||||
try {
|
try {
|
||||||
if (domainFile.isLinkFile()) {
|
|
||||||
// Linked files are always subject to upgrade if needed and do not support
|
|
||||||
// getImmutableDomainObject
|
|
||||||
domainObject =
|
|
||||||
domainFile.getReadOnlyDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
domainObject =
|
domainObject =
|
||||||
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
|
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
String msg = "Could not open file: " + domainFile.getName() +
|
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
|
||||||
"\n\nAvailable export options will be limited.";
|
if (exportPossibleWithoutOpening) {
|
||||||
|
msg += "\n\nAvailable export options will be limited.";
|
||||||
|
}
|
||||||
if (e.isUpgradable()) {
|
if (e.isUpgradable()) {
|
||||||
msg += "\n\nA data upgrade is required. You may open file" +
|
msg += "\n\nA " + linkedPrefix +
|
||||||
"\nin a tool first then Export if a different exporter" + "\nis required.";
|
"content upgrade is required. You may open file in a" +
|
||||||
|
"\ntool first to complete upgrade then Export if needed.";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg += "\nFile was created with a newer version of Ghidra";
|
msg += "\nFile was created with a newer version of Ghidra";
|
||||||
|
@ -505,8 +548,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
Msg.showError(this, getComponent(), "Error Opening File", msg);
|
Msg.showError(this, getComponent(), "Error Opening File", msg);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
String msg = "Could not open file: " + domainFile.getName() +
|
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
|
||||||
"\n\nAvailable export options will be limited.";
|
if (exportPossibleWithoutOpening) {
|
||||||
|
msg += "\n\nAvailable export options will be limited.";
|
||||||
|
}
|
||||||
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
|
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
|
@ -552,7 +597,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
|
|
||||||
boolean exportDomainFile =
|
boolean exportDomainFile =
|
||||||
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
|
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
|
||||||
if (!exportDomainFile && domainFile == null) {
|
if (!exportDomainFile && (domainFile == null || domainFile.isLink())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,9 +70,8 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||||
protected void actionPerformed(NavigatableActionContext context) {
|
protected void actionPerformed(NavigatableActionContext context) {
|
||||||
Program program = context.getProgram();
|
Program program = context.getProgram();
|
||||||
DomainFile domainFile = program.getDomainFile();
|
DomainFile domainFile = program.getDomainFile();
|
||||||
ExporterDialog dialog =
|
ExporterDialog.showExporterDialog(tool, domainFile, program,
|
||||||
new ExporterDialog(tool, domainFile, program, context.getSelection());
|
context.getSelection());
|
||||||
tool.showDialog(dialog);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MenuData menuData =
|
MenuData menuData =
|
||||||
|
@ -104,8 +103,7 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||||
@Override
|
@Override
|
||||||
protected void actionPerformed(ProjectDataContext context) {
|
protected void actionPerformed(ProjectDataContext context) {
|
||||||
DomainFile domainFile = context.getSelectedFiles().get(0);
|
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||||
ExporterDialog dialog = new ExporterDialog(tool, domainFile);
|
ExporterDialog.show(tool, domainFile);
|
||||||
tool.showDialog(dialog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,6 +116,10 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||||
if (selectedFiles.size() != 1) {
|
if (selectedFiles.size() != 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||||
|
if (domainFile.isLink() && domainFile.getLinkInfo().isFolderLink()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.gotoquery;
|
package ghidra.app.plugin.core.gotoquery;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
|
||||||
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -32,7 +30,7 @@ import ghidra.app.util.NamespaceUtils;
|
||||||
import ghidra.app.util.SymbolPath;
|
import ghidra.app.util.SymbolPath;
|
||||||
import ghidra.app.util.query.TableService;
|
import ghidra.app.util.query.TableService;
|
||||||
import ghidra.framework.cmd.Command;
|
import ghidra.framework.cmd.Command;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.ProgramFileChooser;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.model.ProjectData;
|
import ghidra.framework.model.ProjectData;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -106,8 +104,10 @@ public class GoToHelper {
|
||||||
ExternalLocation externalLoc =
|
ExternalLocation externalLoc =
|
||||||
program.getExternalManager().getExternalLocation(externalSym);
|
program.getExternalManager().getExternalLocation(externalSym);
|
||||||
|
|
||||||
// TODO - this seems like a mistake to always pass 'false' here; please doc why we
|
// TODO - This seems like a mistake to always pass 'false' here; please doc why we
|
||||||
// wish to ignore the user options for when to navigate to external programs
|
// wish to ignore the user options for when to navigate to external programs.
|
||||||
|
// It appears this was done since this method is invoked on simple external
|
||||||
|
// location node selection within symbol tree where you would not want a popup.
|
||||||
return goToExternalLinkage(navigatable, externalLoc, false);
|
return goToExternalLinkage(navigatable, externalLoc, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,10 +187,11 @@ public class GoToHelper {
|
||||||
*
|
*
|
||||||
* @param nav Navigatable
|
* @param nav Navigatable
|
||||||
* @param externalLoc external location
|
* @param externalLoc external location
|
||||||
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist,
|
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist
|
||||||
* otherwise navigation to the first linkage location will be performed
|
* or navigation to an external program, otherwise navigation to the first linkage
|
||||||
|
* location will be performed
|
||||||
* @return true if navigation was successful or a list of possible linkage locations was
|
* @return true if navigation was successful or a list of possible linkage locations was
|
||||||
* displayed.
|
* displayed, false if no navigation was performed.
|
||||||
*/
|
*/
|
||||||
protected boolean goToExternalLinkage(Navigatable nav, ExternalLocation externalLoc,
|
protected boolean goToExternalLinkage(Navigatable nav, ExternalLocation externalLoc,
|
||||||
boolean popupAllowed) {
|
boolean popupAllowed) {
|
||||||
|
@ -205,8 +206,14 @@ public class GoToHelper {
|
||||||
NavigationUtils.getExternalLinkageAddresses(program, externalSym.getAddress());
|
NavigationUtils.getExternalLinkageAddresses(program, externalSym.getAddress());
|
||||||
if (externalLinkageAddresses.length == 0) {
|
if (externalLinkageAddresses.length == 0) {
|
||||||
if (externalLoc.isFunction()) {
|
if (externalLoc.isFunction()) {
|
||||||
tool.setStatusInfo("Failed to identify external linkage address for " +
|
// This assume external functions always require linkage location
|
||||||
externalSym.getName(true) + ". Unable to perform navigation.", true);
|
tool.setStatusInfo("Failed to identify external linkage address for function " +
|
||||||
|
externalSym.getName(true), true);
|
||||||
|
}
|
||||||
|
else if (popupAllowed) {
|
||||||
|
// If there are no linkage location try to navigate to external program if a popup
|
||||||
|
// is tolerated.
|
||||||
|
return goToExternalLocation(nav, externalLoc, false);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -306,7 +313,7 @@ public class GoToHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectData pd = tool.getProject().getProjectData();
|
ProjectData pd = tool.getProject().getProjectData();
|
||||||
DomainFile domainFile = pd.getFile(pathName);
|
DomainFile domainFile = pd.getFile(pathName, ProgramFileChooser.PROGRAM_FILE_FILTER);
|
||||||
ProgramManager service = tool.getService(ProgramManager.class);
|
ProgramManager service = tool.getService(ProgramManager.class);
|
||||||
if (domainFile == null || service == null) {
|
if (domainFile == null || service == null) {
|
||||||
tool.setStatusInfo("Unable to navigate to external location. " +
|
tool.setStatusInfo("Unable to navigate to external location. " +
|
||||||
|
@ -441,8 +448,8 @@ public class GoToHelper {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataTreeDialog dialog = new DataTreeDialog(null,
|
ProgramFileChooser dialog =
|
||||||
"Choose External Program (" + extProgName + ")", OPEN);
|
new ProgramFileChooser(null, "Choose External Program (" + extProgName + ")");
|
||||||
dialog.setSearchText(extProgName);
|
dialog.setSearchText(extProgName);
|
||||||
dialog.setHelpLocation(new HelpLocation("ReferencesPlugin", "ChooseExternalProgram"));
|
dialog.setHelpLocation(new HelpLocation("ReferencesPlugin", "ChooseExternalProgram"));
|
||||||
tool.showDialog(dialog);
|
tool.showDialog(dialog);
|
||||||
|
|
|
@ -79,16 +79,25 @@ public class AboutProgramPlugin extends Plugin implements ApplicationLevelPlugin
|
||||||
@Override
|
@Override
|
||||||
protected void actionPerformed(ProjectDataContext context) {
|
protected void actionPerformed(ProjectDataContext context) {
|
||||||
DomainFile domainFile = context.getSelectedFiles().get(0);
|
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||||
showAbout(domainFile, domainFile.getMetadata());
|
Map<String, String> metadata = domainFile.getMetadata();
|
||||||
|
|
||||||
|
showAbout(domainFile, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isAddToPopup(ProjectDataContext context) {
|
protected boolean isAddToPopup(ProjectDataContext context) {
|
||||||
return context.getFileCount() == 1 && context.getFolderCount() == 0;
|
if (context.getFileCount() == 1 && context.getFolderCount() == 0) {
|
||||||
|
// Adjust popup menu text
|
||||||
|
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||||
|
String contentType = domainFile.getContentType();
|
||||||
|
setPopupMenuData(
|
||||||
|
new MenuData(new String[] { "About " + contentType }, null, "AAA"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
aboutAction.setPopupMenuData(
|
aboutAction.setPopupMenuData(new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
|
||||||
new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
|
|
||||||
|
|
||||||
aboutAction.setEnabled(true);
|
aboutAction.setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,10 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file.isLink() && file.getLinkInfo().isExternalLink()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return file.isInWritableProject() &&
|
return file.isInWritableProject() &&
|
||||||
Program.class.isAssignableFrom(file.getDomainObjectClass());
|
Program.class.isAssignableFrom(file.getDomainObjectClass());
|
||||||
}
|
}
|
||||||
|
@ -105,12 +109,12 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
setLanguageAction.setPopupMenuData(
|
setLanguageAction
|
||||||
new MenuData(new String[] { "Set Language..." }, "Language"));
|
.setPopupMenuData(new MenuData(new String[] { "Set Language..." }, "Language"));
|
||||||
|
|
||||||
setLanguageAction.setEnabled(true);
|
setLanguageAction.setEnabled(true);
|
||||||
setLanguageAction.setHelpLocation(
|
setLanguageAction
|
||||||
new HelpLocation("LanguageProviderPlugin", "set language"));
|
.setHelpLocation(new HelpLocation("LanguageProviderPlugin", "set language"));
|
||||||
tool.addAction(setLanguageAction);
|
tool.addAction(setLanguageAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,10 @@ import java.net.URL;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import ghidra.framework.data.DomainFileProxy;
|
import ghidra.framework.data.DomainFileProxy;
|
||||||
import ghidra.framework.data.LinkHandler;
|
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Programs locations can be specified from either a {@link DomainFile} or a ghidra {@link URL}.
|
* Programs locations can be specified from either a {@link DomainFile} or a ghidra {@link URL}.
|
||||||
|
@ -81,11 +81,19 @@ public class ProgramLocator {
|
||||||
file = domainFile;
|
file = domainFile;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (domainFile instanceof LinkedDomainFile linkedFile) {
|
||||||
try {
|
try {
|
||||||
url = GhidraURL.getNormalizedURL(resolveURL(domainFile));
|
// Attempt to resolve to actual linked-file to allow for
|
||||||
|
// direct URL reference
|
||||||
|
domainFile = linkedFile.getLinkedFile();
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
file = domainFile;
|
Msg.error(this, "Failed to resolve linked-file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = domainFile.getLocalProjectURL(null);
|
||||||
|
if (url == null) {
|
||||||
|
url = domainFile.getSharedProjectURL(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.domainFile = file;
|
this.domainFile = file;
|
||||||
|
@ -177,25 +185,4 @@ public class ProgramLocator {
|
||||||
Objects.equals(ghidraURL, other.ghidraURL) && version == other.version;
|
Objects.equals(ghidraURL, other.ghidraURL) && version == other.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
private URL resolveURL(DomainFile file) throws IOException {
|
|
||||||
if (file.isLinkFile()) {
|
|
||||||
return LinkHandler.getURL(file);
|
|
||||||
}
|
|
||||||
DomainFolder parent = file.getParent();
|
|
||||||
if (file instanceof LinkedDomainFile linkedFile) {
|
|
||||||
return resolveLinkedDomainFile(linkedFile);
|
|
||||||
}
|
|
||||||
if (!parent.getProjectLocator().isTransient()) {
|
|
||||||
return file.getLocalProjectURL(null);
|
|
||||||
}
|
|
||||||
return file.getSharedProjectURL(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL resolveLinkedDomainFile(LinkedDomainFile linkedFile) {
|
|
||||||
URL url = linkedFile.getLocalProjectURL(null);
|
|
||||||
if (url == null) {
|
|
||||||
url = linkedFile.getSharedProjectURL(null);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,23 +41,12 @@ class ProgramSaveManager {
|
||||||
private ProgramManager programMgr;
|
private ProgramManager programMgr;
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
private boolean treeDialogCancelled;
|
private boolean treeDialogCancelled;
|
||||||
private DomainFileFilter domainFileFilter;
|
private DomainFileFilter programFileFilter;
|
||||||
|
|
||||||
ProgramSaveManager(PluginTool tool, ProgramManager programMgr) {
|
ProgramSaveManager(PluginTool tool, ProgramManager programMgr) {
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.programMgr = programMgr;
|
this.programMgr = programMgr;
|
||||||
domainFileFilter = new DomainFileFilter() {
|
programFileFilter = new DefaultDomainFileFilter(Program.class, true);
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean accept(DomainFile df) {
|
|
||||||
return Program.class.isAssignableFrom(df.getDomainObjectClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean followLinkedFolders() {
|
|
||||||
return false; // can't save to linked-folder (read-only)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -443,8 +432,7 @@ class ProgramSaveManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataTreeDialog getSaveDialog() {
|
private DataTreeDialog getSaveDialog() {
|
||||||
DataTreeDialog dialog =
|
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, programFileFilter);
|
||||||
new DataTreeDialog(null, "Save As", SAVE, domainFileFilter);
|
|
||||||
|
|
||||||
ActionListener listener = event -> {
|
ActionListener listener = event -> {
|
||||||
DomainFolder folder = dialog.getDomainFolder();
|
DomainFolder folder = dialog.getDomainFolder();
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.references;
|
package ghidra.app.plugin.core.references;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.FlowLayout;
|
import java.awt.FlowLayout;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
@ -30,7 +28,7 @@ import javax.swing.event.DocumentListener;
|
||||||
import docking.widgets.combobox.GhidraComboBox;
|
import docking.widgets.combobox.GhidraComboBox;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
import ghidra.app.util.AddressInput;
|
import ghidra.app.util.AddressInput;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.ProgramFileChooser;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
@ -113,8 +111,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editButton = new JButton("Edit");
|
editButton = new JButton("Select...");
|
||||||
editButton.setToolTipText("Edit Link to External Program");
|
editButton.setToolTipText("Select External Program");
|
||||||
editButton.addActionListener(new ActionListener() {
|
editButton.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
@ -187,10 +185,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
|
||||||
* Pop up the data tree dialog so the user can choose the external program.
|
* Pop up the data tree dialog so the user can choose the external program.
|
||||||
*/
|
*/
|
||||||
private void popupProgramChooser() {
|
private void popupProgramChooser() {
|
||||||
DataTreeDialog d =
|
ProgramFileChooser dialog = new ProgramFileChooser(this.getParent(), "Choose External Program");
|
||||||
new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
|
dialog.addOkActionListener(new ActionListener() {
|
||||||
final DataTreeDialog dialog = d;
|
|
||||||
d.addOkActionListener(new ActionListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
DomainFile df = dialog.getDomainFile();
|
DomainFile df = dialog.getDomainFile();
|
||||||
|
@ -206,7 +202,7 @@ class EditExternalReferencePanel extends EditReferencePanel {
|
||||||
extLibPath.setText(df.getPathname());
|
extLibPath.setText(df.getPathname());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
plugin.getTool().showDialog(d);
|
plugin.getTool().showDialog(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.references;
|
package ghidra.app.plugin.core.references;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -34,7 +32,7 @@ import generic.theme.GIcon;
|
||||||
import ghidra.app.cmd.refs.*;
|
import ghidra.app.cmd.refs.*;
|
||||||
import ghidra.framework.cmd.Command;
|
import ghidra.framework.cmd.Command;
|
||||||
import ghidra.framework.cmd.CompoundCmd;
|
import ghidra.framework.cmd.CompoundCmd;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.*;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.model.DomainObjectListener;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
|
@ -236,8 +234,8 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
|
||||||
private void setExternalProgramAssociation() {
|
private void setExternalProgramAssociation() {
|
||||||
List<String> selectedExternalNames = getSelectedExternalNames();
|
List<String> selectedExternalNames = getSelectedExternalNames();
|
||||||
String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled.
|
String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled.
|
||||||
DataTreeDialog dialog = new DataTreeDialog(mainPanel,
|
DataTreeDialog dialog = new ProgramFileChooser(mainPanel,
|
||||||
"Choose External Program (" + externalName + ")", OPEN);
|
"Choose External Program (" + externalName + ")", AppInfo.getActiveProject());
|
||||||
|
|
||||||
dialog.setSearchText(externalName);
|
dialog.setSearchText(externalName);
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.symboltree;
|
package ghidra.app.plugin.core.symboltree;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ItemListener;
|
import java.awt.event.ItemListener;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -36,7 +34,7 @@ import docking.widgets.label.GLabel;
|
||||||
import ghidra.app.util.AddressInput;
|
import ghidra.app.util.AddressInput;
|
||||||
import ghidra.app.util.NamespaceUtils;
|
import ghidra.app.util.NamespaceUtils;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.ProgramFileChooser;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
@ -270,9 +268,9 @@ class EditExternalLocationPanel extends JPanel {
|
||||||
* Pop up the data tree dialog so the user can choose the external program.
|
* Pop up the data tree dialog so the user can choose the external program.
|
||||||
*/
|
*/
|
||||||
private void popupProgramChooser() {
|
private void popupProgramChooser() {
|
||||||
DataTreeDialog d = new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
|
ProgramFileChooser dialog =
|
||||||
final DataTreeDialog dialog = d;
|
new ProgramFileChooser(this.getParent(), "Choose External Program");
|
||||||
d.addOkActionListener(e -> {
|
dialog.addOkActionListener(e -> {
|
||||||
DomainFile df = dialog.getDomainFile();
|
DomainFile df = dialog.getDomainFile();
|
||||||
if (df == null) {
|
if (df == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -285,7 +283,7 @@ class EditExternalLocationPanel extends JPanel {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
extLibPathTextField.setText(df.getPathname());
|
extLibPathTextField.setText(df.getPathname());
|
||||||
});
|
});
|
||||||
DockingWindowManager.showDialog(this, d);
|
DockingWindowManager.showDialog(this, dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
|
@ -363,7 +361,8 @@ class EditExternalLocationPanel extends JPanel {
|
||||||
|
|
||||||
Project project = AppInfo.getActiveProject();
|
Project project = AppInfo.getActiveProject();
|
||||||
ProjectData projectData = project.getProjectData();
|
ProjectData projectData = project.getProjectData();
|
||||||
DomainFile file = projectData.getFile(extLibPath);
|
DomainFile file =
|
||||||
|
projectData.getFile(extLibPath, ProgramFileChooser.PROGRAM_FILE_FILTER);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
showInputErr("Cannot find the program for the specified library 'Path' of " +
|
showInputErr("Cannot find the program for the specified library 'Path' of " +
|
||||||
extLibPath + ".");
|
extLibPath + ".");
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.symboltree.actions;
|
package ghidra.app.plugin.core.symboltree.actions;
|
||||||
|
|
||||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
|
@ -29,7 +27,7 @@ import ghidra.app.plugin.core.symboltree.*;
|
||||||
import ghidra.app.plugin.core.symboltree.nodes.LibrarySymbolNode;
|
import ghidra.app.plugin.core.symboltree.nodes.LibrarySymbolNode;
|
||||||
import ghidra.framework.cmd.Command;
|
import ghidra.framework.cmd.Command;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.ProgramFileChooser;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.ExternalManager;
|
import ghidra.program.model.symbol.ExternalManager;
|
||||||
|
@ -82,8 +80,8 @@ public class SetExternalProgramAction extends SymbolTreeContextAction {
|
||||||
ExternalManager externalManager = program.getExternalManager();
|
ExternalManager externalManager = program.getExternalManager();
|
||||||
final String externalLibraryPath = externalManager.getExternalLibraryPath(externalName);
|
final String externalLibraryPath = externalManager.getExternalLibraryPath(externalName);
|
||||||
|
|
||||||
final DataTreeDialog dialog = new DataTreeDialog(provider.getComponent(),
|
ProgramFileChooser dialog = new ProgramFileChooser(provider.getComponent(),
|
||||||
"Choose External Program (" + externalName + ")", OPEN);
|
"Choose External Program (" + externalName + ")");
|
||||||
|
|
||||||
dialog.setSearchText(externalName);
|
dialog.setSearchText(externalName);
|
||||||
|
|
||||||
|
|
|
@ -2827,8 +2827,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
DomainFile choice = loadAskValue(this::parseDomainFile, title);
|
DomainFile choice = loadAskValue(this::parseDomainFile, title);
|
||||||
if (!isRunningHeadless()) {
|
if (!isRunningHeadless()) {
|
||||||
choice = doAsk(Program.class, title, "", choice, lastValue -> {
|
choice = doAsk(Program.class, title, "", choice, lastValue -> {
|
||||||
|
// File filter employed limits access to program files within the active project
|
||||||
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
|
// only to ensure the ability to open for update is possible.
|
||||||
|
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN,
|
||||||
|
new DefaultDomainFileFilter(Program.class, true));
|
||||||
dtd.show();
|
dtd.show();
|
||||||
if (dtd.wasCancelled()) {
|
if (dtd.wasCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -2932,8 +2934,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
|
|
||||||
String message = "";
|
String message = "";
|
||||||
DomainFile choice = doAsk(DomainFile.class, title, message, existingValue, lastValue -> {
|
DomainFile choice = doAsk(DomainFile.class, title, message, existingValue, lastValue -> {
|
||||||
|
// File filter employed limits access to files within the active project
|
||||||
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
|
// only to ensure the ability to open for update is possible.
|
||||||
|
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN,
|
||||||
|
DomainFileFilter.ALL_FILES_NO_EXTERNAL_FOLDERS_FILTER);
|
||||||
dtd.show();
|
dtd.show();
|
||||||
if (dtd.wasCancelled()) {
|
if (dtd.wasCancelled()) {
|
||||||
throw new CancelledException();
|
throw new CancelledException();
|
||||||
|
|
|
@ -47,7 +47,8 @@ public class GdtExporter extends Exporter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||||
return canExportDomainObject(domainFile.getDomainObjectClass());
|
// Avoid exporting link-file itself
|
||||||
|
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -42,7 +42,8 @@ public class GzfExporter extends Exporter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||||
return canExportDomainObject(domainFile.getDomainObjectClass());
|
// Avoid exporting link-file itself
|
||||||
|
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -43,6 +43,7 @@ import ghidra.framework.model.*;
|
||||||
import ghidra.framework.project.DefaultProject;
|
import ghidra.framework.project.DefaultProject;
|
||||||
import ghidra.framework.project.DefaultProjectManager;
|
import ghidra.framework.project.DefaultProjectManager;
|
||||||
import ghidra.framework.protocol.ghidra.*;
|
import ghidra.framework.protocol.ghidra.*;
|
||||||
|
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
|
||||||
import ghidra.framework.remote.User;
|
import ghidra.framework.remote.User;
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
|
@ -354,7 +355,10 @@ public class HeadlessAnalyzer {
|
||||||
}
|
}
|
||||||
throw new IOException(title + ": " + message);
|
throw new IOException(title + ": " + message);
|
||||||
}
|
}
|
||||||
}, TaskMonitor.DUMMY);
|
|
||||||
|
// Link files are skipped to avoid duplicate processing
|
||||||
|
// Processing should be done on actual folder - not a linked folder
|
||||||
|
}, LinkFileControl.NO_FOLLOW, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
|
@ -1338,7 +1342,7 @@ public class HeadlessAnalyzer {
|
||||||
boolean filesProcessed = false;
|
boolean filesProcessed = false;
|
||||||
|
|
||||||
DomainFile domFile = parentFolder.getFile(filename);
|
DomainFile domFile = parentFolder.getFile(filename);
|
||||||
// Do not follow folder-links or consider program links. Using content type
|
// Do not follow folder-links or program links. Using content type
|
||||||
// to filter is best way to control this.
|
// to filter is best way to control this.
|
||||||
if (domFile != null &&
|
if (domFile != null &&
|
||||||
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
|
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.framework.client.RepositoryAdapter;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURLQuery;
|
import ghidra.framework.protocol.ghidra.GhidraURLQuery;
|
||||||
|
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURLResultHandlerAdapter;
|
import ghidra.framework.protocol.ghidra.GhidraURLResultHandlerAdapter;
|
||||||
import ghidra.framework.remote.User;
|
import ghidra.framework.remote.User;
|
||||||
import ghidra.framework.store.ExclusiveCheckoutException;
|
import ghidra.framework.store.ExclusiveCheckoutException;
|
||||||
|
@ -103,13 +104,13 @@ public class ProgramOpener {
|
||||||
|
|
||||||
AtomicReference<Program> openedProgram = new AtomicReference<>();
|
AtomicReference<Program> openedProgram = new AtomicReference<>();
|
||||||
try {
|
try {
|
||||||
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
|
GhidraURLQuery.queryUrl(ghidraUrl, Program.class, new GhidraURLResultHandlerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
|
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
|
||||||
Program p = openProgram(locator, domainFile, m); // may return null
|
Program p = openProgram(locator, domainFile, m); // may return null
|
||||||
openedProgram.set(p);
|
openedProgram.set(p);
|
||||||
}
|
}
|
||||||
}, monitor);
|
}, LinkFileControl.FOLLOW_EXTERNAL, monitor);
|
||||||
}
|
}
|
||||||
catch (IOException | CancelledException e) {
|
catch (IOException | CancelledException e) {
|
||||||
// IOException reported to user by GhidraURLResultHandlerAdapter
|
// IOException reported to user by GhidraURLResultHandlerAdapter
|
||||||
|
@ -148,7 +149,7 @@ public class ProgramOpener {
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
String contentType = domainFile.getContentType();
|
String contentType = domainFile.getContentType();
|
||||||
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", e);
|
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", false, e);
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
// we don't care, the task has been cancelled
|
// we don't care, the task has been cancelled
|
||||||
|
@ -197,7 +198,7 @@ public class ProgramOpener {
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
|
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
|
||||||
"Open", e);
|
"Open", false, e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,31 +129,37 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||||
serviceProvider.getService(ProjectDataService.class);
|
serviceProvider.getService(ProjectDataService.class);
|
||||||
ProjectData projectData = projectDataService.getProjectData();
|
ProjectData projectData = projectDataService.getProjectData();
|
||||||
|
|
||||||
// default folder is the root folder
|
|
||||||
DomainFolder folder = projectData.getRootFolder();
|
|
||||||
|
|
||||||
// Get program name and folder from program comment annotation
|
// Get program name and folder from program comment annotation
|
||||||
// handles forward and back slashes and with and without first slash
|
// handles forward and back slashes and with and without first slash
|
||||||
String programText = getProgramText(annotationParts);
|
String programText = getProgramText(annotationParts);
|
||||||
String programName = FilenameUtils.getName(programText);
|
String programName = FilenameUtils.getName(programText);
|
||||||
String path = FilenameUtils.getFullPathNoEndSeparator(programText);
|
String path = FilenameUtils.getFullPathNoEndSeparator(programText);
|
||||||
|
|
||||||
|
DomainFolder folder;
|
||||||
if (path.length() > 0) {
|
if (path.length() > 0) {
|
||||||
path = StringUtils.prependIfMissing(FilenameUtils.separatorsToUnix(path), "/");
|
path = StringUtils.prependIfMissing(FilenameUtils.separatorsToUnix(path), "/");
|
||||||
folder = projectData.getFolder(path);
|
folder = projectData.getFolder(path);
|
||||||
}
|
|
||||||
|
|
||||||
if (folder == null) {
|
if (folder == null) {
|
||||||
Msg.showInfo(getClass(), null, "No Folder: " + path,
|
Msg.showInfo(getClass(), null, "Folder Not Found: " + path,
|
||||||
"Unable to locate folder by the name \"" + path);
|
"Unable to locate folder by the name \"" + path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
folder = projectData.getRootFolder();
|
||||||
|
}
|
||||||
|
|
||||||
DomainFile programFile = findProgramByName(programName, folder);
|
DomainFile programFile = folder.getFile(programName);
|
||||||
|
if (programFile == null ||
|
||||||
if (programFile == null) {
|
!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
|
||||||
Msg.showInfo(getClass(), null, "No Program: " + programName,
|
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
|
||||||
"Unable to locate a program by the name \"" + programName +
|
"Unable to locate program at path \"" + programText +
|
||||||
"\".\nNOTE: Program name is case-sensitive. ");
|
"\".\nNOTE: File names are case-sensitive.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
|
||||||
|
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
|
||||||
|
"File exists with incorrect content type. ");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +205,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg.showInfo(getClass(), null, "No Symbol: " + symbolName,
|
Msg.showInfo(getClass(), null, "Symbol Not Found: " + symbolName,
|
||||||
"Unable to navigate to '" + symbolName + "' in the program '" + programFile.getName() +
|
"Unable to navigate to '" + symbolName + "' in the program '" + programFile.getName() +
|
||||||
"'.\nMake sure that the given symbol/address exists.");
|
"'.\nMake sure that the given symbol/address exists.");
|
||||||
if (!programManager.isVisible(program)) {
|
if (!programManager.isVisible(program)) {
|
||||||
|
@ -247,27 +253,6 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursive program to find a program by the given name within the given folder
|
|
||||||
private DomainFile findProgramByName(String programText, DomainFolder folder) {
|
|
||||||
DomainFile[] files = folder.getFiles();
|
|
||||||
for (DomainFile file : files) {
|
|
||||||
if (file.getName().equals(programText)) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// not at the current level, then check sub-folders
|
|
||||||
DomainFolder[] folders = folder.getFolders();
|
|
||||||
for (DomainFolder subFolder : folders) {
|
|
||||||
DomainFile domainFile = findProgramByName(programText, subFolder);
|
|
||||||
if (domainFile != null) {
|
|
||||||
return domainFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayString() {
|
public String getDisplayString() {
|
||||||
return "Program";
|
return "Program";
|
||||||
|
@ -275,7 +260,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPrototypeString() {
|
public String getPrototypeString() {
|
||||||
return "{@program program_name.exe@symbol_name}";
|
return "{@program program_path@symbol_name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -291,16 +291,28 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
||||||
else {
|
else {
|
||||||
domainFile = treePanel.getSelectedDomainFile();
|
domainFile = treePanel.getSelectedDomainFile();
|
||||||
if (domainFile != null) {
|
if (domainFile != null) {
|
||||||
|
LinkFileInfo linkInfo = domainFile.getLinkInfo();
|
||||||
|
if (linkInfo != null && linkInfo.isFolderLink()) {
|
||||||
|
// Ensure we don't have a folder name conflict
|
||||||
|
if (domainFile.getParent().getFolder(domainFile.getName()) == null) {
|
||||||
|
domainFolder = linkInfo.getLinkedFolder();
|
||||||
|
domainFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
folderNameLabel.setText(domainFile.getParent().getPathname());
|
folderNameLabel.setText(domainFile.getParent().getPathname());
|
||||||
nameField.setText(domainFile.getName());
|
nameField.setText(domainFile.getName());
|
||||||
domainFolder = domainFile.getParent();
|
domainFolder = domainFile.getParent();
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
|
|
||||||
|
if (domainFile == null) {
|
||||||
|
if (domainFolder == null) {
|
||||||
domainFolder = treePanel.getSelectedDomainFolder();
|
domainFolder = treePanel.getSelectedDomainFolder();
|
||||||
if (domainFolder == null) {
|
if (domainFolder == null) {
|
||||||
domainFolder = project.getProjectData().getRootFolder();
|
domainFolder = project.getProjectData().getRootFolder();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
folderNameLabel.setText(domainFolder.getPathname());
|
folderNameLabel.setText(domainFolder.getPathname());
|
||||||
if (nameField.isEditable()) {
|
if (nameField.isEditable()) {
|
||||||
if (nameField.getText().length() > 0) {
|
if (nameField.getText().length() > 0) {
|
||||||
|
@ -349,8 +361,10 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
||||||
* @param file the file
|
* @param file the file
|
||||||
*/
|
*/
|
||||||
public void selectDomainFile(DomainFile file) {
|
public void selectDomainFile(DomainFile file) {
|
||||||
|
if (file != null) {
|
||||||
Swing.runLater(() -> treePanel.selectDomainFile(file));
|
Swing.runLater(() -> treePanel.selectDomainFile(file));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
@ -584,20 +598,6 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static DomainFileFilter getDefaultFilter(DataTreeDialogType type) {
|
|
||||||
if (type == CHOOSE_FOLDER || type == OPEN) {
|
|
||||||
// return filter which forces folder selection and allow navigation into linked-folders
|
|
||||||
return new DomainFileFilter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean accept(DomainFile df) {
|
|
||||||
return true; // show all files (legacy behavior)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FieldKeyListener extends KeyAdapter {
|
private class FieldKeyListener extends KeyAdapter {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
|
|
|
@ -17,8 +17,7 @@ package ghidra.framework.main;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
|
||||||
import ghidra.framework.model.DomainFileFilter;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.model.Project;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog to open or save domain data items to a new location or name.
|
* Dialog to open or save domain data items to a new location or name.
|
||||||
|
@ -33,9 +32,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new DataTreeDialog for the active project. This chooser will show all project
|
* Construct a new DataTreeDialog for the active project. This chooser will show all project
|
||||||
* files. Following linked-folders will only be allowed if a type of CHOOSE_FOLDER
|
* files and/or folders within the active project only. Broken and external links will not be
|
||||||
* or OPEN is specified. If different behavior is required a filter should
|
* shown. If different behavior is required a filter should be specified using the other
|
||||||
* be specified using the other constructor.
|
* constructor.
|
||||||
*
|
*
|
||||||
* @param parent dialog's parent
|
* @param parent dialog's parent
|
||||||
* @param title title to use
|
* @param title title to use
|
||||||
|
@ -43,7 +42,7 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
||||||
* @throws IllegalArgumentException if invalid type is specified
|
* @throws IllegalArgumentException if invalid type is specified
|
||||||
*/
|
*/
|
||||||
public DataTreeDialog(Component parent, String title, DataTreeDialogType type) {
|
public DataTreeDialog(Component parent, String title, DataTreeDialogType type) {
|
||||||
this(parent, title, type, getDefaultFilter(type));
|
this(parent, title, type, DomainFileFilter.ALL_INTERNAL_FILES_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,7 +51,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
||||||
* @param parent dialog's parent
|
* @param parent dialog's parent
|
||||||
* @param title title to use
|
* @param title title to use
|
||||||
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
|
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
|
||||||
* @param filter filter used to control what is displayed in the data tree
|
* @param filter filter used to control what is displayed in the data tree. See static
|
||||||
|
* implementations provided by {@link DomainFileFilter} and a more tailored
|
||||||
|
* {@link DefaultDomainFileFilter}.
|
||||||
* @throws IllegalArgumentException if invalid type is specified
|
* @throws IllegalArgumentException if invalid type is specified
|
||||||
*/
|
*/
|
||||||
public DataTreeDialog(Component parent, String title, DataTreeDialogType type,
|
public DataTreeDialog(Component parent, String title, DataTreeDialogType type,
|
||||||
|
@ -66,7 +67,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
||||||
* @param parent dialog's parent
|
* @param parent dialog's parent
|
||||||
* @param title title to use
|
* @param title title to use
|
||||||
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
|
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
|
||||||
* @param filter filter used to control what is displayed in the data tree
|
* @param filter filter used to control what is displayed in the data tree. See static
|
||||||
|
* implementations provided by {@link DomainFileFilter} and a more tailored
|
||||||
|
* {@link DefaultDomainFileFilter}.
|
||||||
* @param project the project to browse
|
* @param project the project to browse
|
||||||
* @throws IllegalArgumentException if invalid type is specified
|
* @throws IllegalArgumentException if invalid type is specified
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -90,9 +90,8 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
|
||||||
*/
|
*/
|
||||||
public OpenVersionedFileDialog(PluginTool tool, String title, Class<T> domainObjectClass,
|
public OpenVersionedFileDialog(PluginTool tool, String title, Class<T> domainObjectClass,
|
||||||
List<T> openDomainObjects) {
|
List<T> openDomainObjects) {
|
||||||
super(tool.getToolFrame(), title, OPEN, f -> {
|
super(tool.getToolFrame(), title, OPEN,
|
||||||
return domainObjectClass.isAssignableFrom(f.getDomainObjectClass());
|
new DefaultDomainFileFilter(domainObjectClass, false), AppInfo.getActiveProject());
|
||||||
}, AppInfo.getActiveProject());
|
|
||||||
|
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.domainObjectClass = domainObjectClass;
|
this.domainObjectClass = domainObjectClass;
|
||||||
|
@ -214,8 +213,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
|
||||||
tabbedPane = new JTabbedPane();
|
tabbedPane = new JTabbedPane();
|
||||||
tabbedPane.setName("Tabs");
|
tabbedPane.setName("Tabs");
|
||||||
tabbedPane.add("Project Files", projectFilePanel);
|
tabbedPane.add("Project Files", projectFilePanel);
|
||||||
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s",
|
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s", buildOpenObjectsTable());
|
||||||
buildOpenObjectsTable());
|
|
||||||
|
|
||||||
tabbedPane.addChangeListener(e -> {
|
tabbedPane.addChangeListener(e -> {
|
||||||
int selectedTabIndex = tabbedPane.getModel().getSelectedIndex();
|
int selectedTabIndex = tabbedPane.getModel().getSelectedIndex();
|
||||||
|
@ -254,8 +252,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
|
||||||
|
|
||||||
openObjectsTable = new GFilterTable<>(new OpenObjectsTableModel());
|
openObjectsTable = new GFilterTable<>(new OpenObjectsTableModel());
|
||||||
GTable table = openObjectsTable.getTable();
|
GTable table = openObjectsTable.getTable();
|
||||||
table.getSelectionModel()
|
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
||||||
openObjectsTable.addSelectionListener(e -> {
|
openObjectsTable.addSelectionListener(e -> {
|
||||||
setOkEnabled(true);
|
setOkEnabled(true);
|
||||||
okButton.setToolTipText("Use the selected " + domainObjectClass.getSimpleName());
|
okButton.setToolTipText("Use the selected " + domainObjectClass.getSimpleName());
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.main;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import ghidra.framework.model.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ProgramFileChooser} facilitates selection of an existing project Program file including
|
||||||
|
* Program link-files which may link to either internal or external program files.
|
||||||
|
* This chooser operates in the {@link DataTreeDialogType#OPEN open mode} for selecting
|
||||||
|
* an existing file only.
|
||||||
|
* <P>
|
||||||
|
* This chooser should not be used to facilitate an immediate or
|
||||||
|
* future save-as operation or to open a Program for update since it can return a read-only file.
|
||||||
|
* A more taylored {@link DataTreeDialog} should be used for case where the file will be written.
|
||||||
|
*/
|
||||||
|
public class ProgramFileChooser extends DataTreeDialog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file filter permits selection of any program including those than can be
|
||||||
|
* found by following bother internal and external folder and files links.
|
||||||
|
*/
|
||||||
|
public static final DomainFileFilter PROGRAM_FILE_FILTER =
|
||||||
|
new DefaultDomainFileFilter(Program.class, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new ProgramChooser for the active project.
|
||||||
|
*
|
||||||
|
* @param parent dialog's parent
|
||||||
|
* @param title title to use
|
||||||
|
* @throws IllegalArgumentException if invalid type is specified
|
||||||
|
*/
|
||||||
|
public ProgramFileChooser(Component parent, String title) {
|
||||||
|
super(parent, title, DataTreeDialogType.OPEN, PROGRAM_FILE_FILTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new DataTreeDialog for the given project.
|
||||||
|
*
|
||||||
|
* @param parent dialog's parent
|
||||||
|
* @param title title to use
|
||||||
|
* @param project the project to browse
|
||||||
|
* @throws IllegalArgumentException if invalid type is specified
|
||||||
|
*/
|
||||||
|
public ProgramFileChooser(Component parent, String title, Project project) {
|
||||||
|
super(parent, title, DataTreeDialogType.OPEN, PROGRAM_FILE_FILTER, project);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,7 +19,6 @@ import java.awt.Component;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import docking.widgets.tree.GTreeNode;
|
|
||||||
import ghidra.app.services.FileImporterService;
|
import ghidra.app.services.FileImporterService;
|
||||||
import ghidra.app.util.FileOpenDataFlavorHandler;
|
import ghidra.app.util.FileOpenDataFlavorHandler;
|
||||||
import ghidra.framework.model.DomainFolder;
|
import ghidra.framework.model.DomainFolder;
|
||||||
|
@ -61,15 +60,4 @@ abstract class AbstractFileListFlavorHandler
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DomainFolder getDomainFolder(GTreeNode destinationNode) {
|
|
||||||
if (destinationNode instanceof DomainFolderNode) {
|
|
||||||
return ((DomainFolderNode) destinationNode).getDomainFolder();
|
|
||||||
}
|
|
||||||
else if (destinationNode instanceof DomainFileNode) {
|
|
||||||
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
|
|
||||||
return parent.getDomainFolder();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public final class JavaFileListHandler extends AbstractFileListFlavorHandler {
|
||||||
if (fileList.isEmpty()) {
|
if (fileList.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
doImport(getDomainFolder(destinationNode), fileList, tool, dataTree);
|
doImport(DataTree.getRealInternalFolderForNode(destinationNode), fileList, tool, dataTree);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,8 @@ public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
|
||||||
* Linux URL-based file list {@link DataFlavor} to be used during handler registration
|
* Linux URL-based file list {@link DataFlavor} to be used during handler registration
|
||||||
* using {@link DataTreeDragNDropHandler#addActiveDataFlavorHandler}.
|
* using {@link DataTreeDragNDropHandler#addActiveDataFlavorHandler}.
|
||||||
*/
|
*/
|
||||||
public static final DataFlavor linuxFileUrlFlavor =
|
public static final DataFlavor linuxFileUrlFlavor = new DataFlavor(
|
||||||
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
|
"application/x-java-serialized-object;class=java.lang.String", "String file URL");
|
||||||
"String file URL");
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
|
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
|
||||||
|
@ -57,7 +56,7 @@ public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
doImport(getDomainFolder(destinationNode), files, tool, dataTree);
|
doImport(DataTree.getRealInternalFolderForNode(destinationNode), files, tool, dataTree);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||||
*/
|
*/
|
||||||
public void setDestinationFolder(DomainFolder folder) {
|
public void setDestinationFolder(DomainFolder folder) {
|
||||||
destinationFolder = folder;
|
destinationFolder = folder;
|
||||||
folderNameTextField.setText(destinationFolder.toString());
|
folderNameTextField.setText(destinationFolder.getPathname());
|
||||||
validateFormInput();
|
validateFormInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,7 +521,7 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||||
String parentPath = FilenameUtils.getFullPathNoEndSeparator(pathName);
|
String parentPath = FilenameUtils.getFullPathNoEndSeparator(pathName);
|
||||||
String fileOrFolderName = FilenameUtils.getName(pathName);
|
String fileOrFolderName = FilenameUtils.getName(pathName);
|
||||||
DomainFolder localDestFolder =
|
DomainFolder localDestFolder =
|
||||||
(parentPath != null) ? ProjectDataUtils.lookupDomainPath(destinationFolder, parentPath)
|
(parentPath != null) ? ProjectDataUtils.getDomainFolder(destinationFolder, parentPath)
|
||||||
: destinationFolder;
|
: destinationFolder;
|
||||||
if (localDestFolder != null) {
|
if (localDestFolder != null) {
|
||||||
if (isFolder && localDestFolder.getFolder(fileOrFolderName) != null ||
|
if (isFolder && localDestFolder.getFolder(fileOrFolderName) != null ||
|
||||||
|
|
|
@ -28,6 +28,7 @@ import docking.action.*;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import docking.widgets.filechooser.GhidraFileChooser;
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||||
|
import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.app.CorePluginPackage;
|
import ghidra.app.CorePluginPackage;
|
||||||
import ghidra.app.context.ListingActionContext;
|
import ghidra.app.context.ListingActionContext;
|
||||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||||
|
@ -441,17 +442,14 @@ public class ImporterPlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DomainFolder getFolderFromContext(ActionContext context) {
|
private static DomainFolder getFolderFromContext(ActionContext context) {
|
||||||
|
DomainFolder folder = null;
|
||||||
Object contextObj = context.getContextObject();
|
Object contextObj = context.getContextObject();
|
||||||
if (contextObj instanceof DomainFolderNode) {
|
if (contextObj instanceof GTreeNode dataTreeNode) {
|
||||||
DomainFolderNode node = (DomainFolderNode) contextObj;
|
folder = DataTree.getRealInternalFolderForNode(dataTreeNode);
|
||||||
return node.getDomainFolder();
|
|
||||||
}
|
}
|
||||||
if (contextObj instanceof DomainFileNode) {
|
if (folder != null && folder.isInWritableProject()) {
|
||||||
DomainFileNode node = (DomainFileNode) contextObj;
|
return folder;
|
||||||
DomainFile domainFile = node.getDomainFile();
|
|
||||||
return domainFile != null ? domainFile.getParent() : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppInfo.getActiveProject().getProjectData().getRootFolder();
|
return AppInfo.getActiveProject().getProjectData().getRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.test;
|
package ghidra.test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ghidra.program.database.ProgramBuilder;
|
import ghidra.program.database.ProgramBuilder;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
@ -628,4 +627,21 @@ public class ToyProgramBuilder extends ProgramBuilder {
|
||||||
disassemble(address, 1);
|
disassemble(address, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create simple Toy program with a single initialized memory block at 0x1001000-0x1002fff
|
||||||
|
* @param programName program name
|
||||||
|
* @param consumer object consumer responsible for releasing the returned program
|
||||||
|
* @return new in-memory program instance
|
||||||
|
* @throws Exception if an error occurs
|
||||||
|
*/
|
||||||
|
public static Program buildSimpleProgram(String programName, Object consumer) throws Exception {
|
||||||
|
Objects.requireNonNull(consumer);
|
||||||
|
ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY);
|
||||||
|
builder.createMemory("test1", Long.toHexString(0x1001000), 0x2000);
|
||||||
|
Program p = builder.getProgram();
|
||||||
|
p.addConsumer(consumer);
|
||||||
|
p.release(builder);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,10 @@ import java.awt.*;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -52,7 +52,6 @@ import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.util.bean.field.AnnotatedTextFieldElement;
|
import ghidra.util.bean.field.AnnotatedTextFieldElement;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import util.CollectionUtils;
|
|
||||||
|
|
||||||
public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
|
@ -365,7 +364,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||||
|
|
||||||
assertErrorDialog("No Symbol");
|
assertErrorDialog("Symbol Not Found");
|
||||||
|
|
||||||
assertTrue(spyServiceProvider.programOpened(programName));
|
assertTrue(spyServiceProvider.programOpened(programName));
|
||||||
assertTrue(spyServiceProvider.programClosed(programName));
|
assertTrue(spyServiceProvider.programClosed(programName));
|
||||||
|
@ -422,7 +421,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||||
|
|
||||||
assertErrorDialog("No Symbol");
|
assertErrorDialog("Symbol Not Found");
|
||||||
|
|
||||||
assertTrue(spyServiceProvider.programOpened(programName));
|
assertTrue(spyServiceProvider.programOpened(programName));
|
||||||
assertTrue(spyServiceProvider.programClosed(programName));
|
assertTrue(spyServiceProvider.programClosed(programName));
|
||||||
|
@ -478,7 +477,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||||
|
|
||||||
assertErrorDialog("No Program");
|
assertErrorDialog("Program Not Found");
|
||||||
|
|
||||||
assertFalse(spyServiceProvider.programOpened(programName));
|
assertFalse(spyServiceProvider.programOpened(programName));
|
||||||
}
|
}
|
||||||
|
@ -495,8 +494,8 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
||||||
|
|
||||||
// real path
|
// real path
|
||||||
String realPath = "folder1/program_f1_f2.exe";
|
String realPath = "/folder1/program_f1_f2.exe";
|
||||||
addFakeProgramByPath(spyServiceProvider, realPath);
|
addFakeProgramByPath(spyServiceProvider, realPath, program);
|
||||||
|
|
||||||
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
||||||
String rawComment = "My comment - " + annotationText;
|
String rawComment = "My comment - " + annotationText;
|
||||||
|
@ -513,7 +512,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||||
|
|
||||||
assertErrorDialog("No Folder");
|
assertErrorDialog("Folder Not Found");
|
||||||
|
|
||||||
assertFalse(spyServiceProvider.programOpened(otherProgramPath));
|
assertFalse(spyServiceProvider.programOpened(otherProgramPath));
|
||||||
}
|
}
|
||||||
|
@ -525,7 +524,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
SpyServiceProvider spyServiceProvider = new SpyServiceProvider();
|
SpyServiceProvider spyServiceProvider = new SpyServiceProvider();
|
||||||
|
|
||||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||||
|
|
||||||
String annotationText = "{@program " + otherProgramPath + "}";
|
String annotationText = "{@program " + otherProgramPath + "}";
|
||||||
String rawComment = "My comment - " + annotationText;
|
String rawComment = "My comment - " + annotationText;
|
||||||
|
@ -557,7 +556,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
Address address = program.getAddressFactory().getAddress(addresstring);
|
Address address = program.getAddressFactory().getAddress(addresstring);
|
||||||
|
|
||||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||||
|
|
||||||
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
||||||
String rawComment = "My comment - " + annotationText;
|
String rawComment = "My comment - " + annotationText;
|
||||||
|
@ -591,7 +590,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||||
String annotationPath = "\\folder1\\folder2\\program_f1_f2.exe";
|
String annotationPath = "\\folder1\\folder2\\program_f1_f2.exe";
|
||||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||||
|
|
||||||
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
|
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
|
||||||
String rawComment = "My comment - " + annotationText;
|
String rawComment = "My comment - " + annotationText;
|
||||||
|
@ -622,9 +621,9 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String addresstring = "1001000";
|
String addresstring = "1001000";
|
||||||
Address address = program.getAddressFactory().getAddress(addresstring);
|
Address address = program.getAddressFactory().getAddress(addresstring);
|
||||||
|
|
||||||
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||||
String annotationPath = "folder1\\folder2\\program_f1_f2.exe";
|
String annotationPath = "folder1\\folder2\\program_f1_f2.exe";
|
||||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||||
|
|
||||||
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
|
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
|
||||||
String rawComment = "My comment - " + annotationText;
|
String rawComment = "My comment - " + annotationText;
|
||||||
|
@ -656,8 +655,8 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String addresstring = "1001000";
|
String addresstring = "1001000";
|
||||||
Address address = program.getAddressFactory().getAddress(addresstring);
|
Address address = program.getAddressFactory().getAddress(addresstring);
|
||||||
|
|
||||||
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||||
|
|
||||||
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
||||||
String rawComment = "My comment - " + annotationText;
|
String rawComment = "My comment - " + annotationText;
|
||||||
|
@ -883,30 +882,29 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
return new FieldElement[] { fieldElement };
|
return new FieldElement[] { fieldElement };
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFakeProgramByPath(SpyServiceProvider provider, String path) {
|
private void addFakeProgramByPath(SpyServiceProvider provider, String path, Program p) {
|
||||||
|
|
||||||
SpyProjectDataService spyProjectData =
|
SpyProjectDataService spyProjectData =
|
||||||
(SpyProjectDataService) provider.getService(ProjectDataService.class);
|
(SpyProjectDataService) provider.getService(ProjectDataService.class);
|
||||||
FakeRootFolder root = spyProjectData.fakeProjectData.fakeRootFolder;
|
FakeRootFolder root = spyProjectData.fakeProjectData.fakeRootFolder;
|
||||||
|
|
||||||
String parentPath = FilenameUtils.getFullPath(path);
|
if (StringUtils.isBlank(path) || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
|
||||||
String programName = FilenameUtils.getName(path);
|
throw new IllegalArgumentException(
|
||||||
|
"Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'");
|
||||||
String[] paths = parentPath.split("/");
|
|
||||||
TestDummyDomainFolder parent = root;
|
|
||||||
String pathSoFar = root.getPathname();
|
|
||||||
for (String folderName : paths) {
|
|
||||||
pathSoFar += folderName;
|
|
||||||
TestDummyDomainFolder folder = (TestDummyDomainFolder) root.getFolder(pathSoFar);
|
|
||||||
if (folder == null) {
|
|
||||||
folder = new TestDummyDomainFolder(parent, folderName);
|
|
||||||
root.addFolder(folder);
|
|
||||||
}
|
}
|
||||||
parent = folder;
|
else if (path.charAt(path.length() - 1) == FileSystem.SEPARATOR_CHAR) {
|
||||||
|
throw new IllegalArgumentException("Missing file name in path");
|
||||||
}
|
}
|
||||||
|
int ix = path.lastIndexOf(FileSystem.SEPARATOR);
|
||||||
|
String folderPath = "/";
|
||||||
|
if (ix > 0) {
|
||||||
|
folderPath = path.substring(0, ix);
|
||||||
|
}
|
||||||
|
String programName = path.substring(ix + 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parent.createFile(programName, (DomainObject) null, TaskMonitor.DUMMY);
|
DomainFolder parent = ProjectDataUtils.createDomainFolderPath(root, folderPath);
|
||||||
|
parent.createFile(programName, p, TaskMonitor.DUMMY);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
failWithException("Unable to create a dummy domain file", e);
|
failWithException("Unable to create a dummy domain file", e);
|
||||||
|
@ -973,41 +971,50 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DomainFolder getFolder(String path) {
|
public DomainFolder getFolder(String path, DomainFolderFilter filter) {
|
||||||
return fakeRootFolder.getFolder(path);
|
return ProjectDataUtils.getDomainFolder(fakeRootFolder, path, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DomainFile getFile(String path, DomainFileFilter filter) {
|
||||||
|
if (StringUtils.isBlank(path) || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'");
|
||||||
|
}
|
||||||
|
else if (path.charAt(path.length() - 1) == FileSystem.SEPARATOR_CHAR) {
|
||||||
|
throw new IllegalArgumentException("Missing file name in path");
|
||||||
|
}
|
||||||
|
int ix = path.lastIndexOf(FileSystem.SEPARATOR);
|
||||||
|
|
||||||
|
DomainFolder folder;
|
||||||
|
String fileName = path;
|
||||||
|
if (ix > 0) {
|
||||||
|
folder = getFolder(path.substring(0, ix), filter);
|
||||||
|
fileName = path.substring(ix + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
folder = getRootFolder();
|
||||||
|
}
|
||||||
|
if (folder != null) {
|
||||||
|
DomainFile file = folder.getFile(fileName);
|
||||||
|
if (file != null && filter.accept(file)) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeRootFolder extends TestDummyDomainFolder {
|
private class FakeRootFolder extends TestDummyDomainFolder {
|
||||||
|
|
||||||
private List<TestDummyDomainFolder> folders = CollectionUtils.asList(this);
|
|
||||||
|
|
||||||
private List<TestDummyDomainFile> folderFiles =
|
|
||||||
CollectionUtils.asList(new TestDummyDomainFile(this, OTHER_PROGRAM_NAME));
|
|
||||||
|
|
||||||
public FakeRootFolder() {
|
public FakeRootFolder() {
|
||||||
super(null, "Fake Root Folder");
|
super(null, "Fake Root Folder");
|
||||||
}
|
files.add(new TestDummyDomainFile(this, OTHER_PROGRAM_NAME, "Program"));
|
||||||
|
|
||||||
void addFolder(TestDummyDomainFolder f) {
|
|
||||||
folders.add(f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized DomainFile[] getFiles() {
|
public boolean isInWritableProject() {
|
||||||
return folderFiles.toArray(new TestDummyDomainFile[folderFiles.size()]);
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized DomainFolder getFolder(String path) {
|
|
||||||
for (TestDummyDomainFolder folder : folders) {
|
|
||||||
String folderPath = folder.getPathname();
|
|
||||||
if (folderPath.equals(path)) {
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,10 +87,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
showFiltered("tN");
|
showFiltered("tN");
|
||||||
|
|
||||||
JTree tree = getJTree();
|
JTree tree = getJTree();
|
||||||
List<String> expectedFilteredNames = names.stream()
|
List<String> expectedFilteredNames =
|
||||||
.filter(s -> s.startsWith("tN"))
|
names.stream().filter(s -> s.startsWith("tN")).sorted().collect(Collectors.toList());
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
TreeModel model = tree.getModel();
|
TreeModel model = tree.getModel();
|
||||||
GTreeNode root = (GTreeNode) model.getRoot();
|
GTreeNode root = (GTreeNode) model.getRoot();
|
||||||
|
@ -424,8 +422,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private void showFiltered(final String startsWith) {
|
private void showFiltered(final String startsWith) {
|
||||||
Swing.runLater(() -> {
|
Swing.runLater(() -> {
|
||||||
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog",
|
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", OPEN,
|
||||||
OPEN, f -> f.getName().startsWith(startsWith));
|
f -> f.getName().startsWith(startsWith));
|
||||||
dialog.showComponent();
|
dialog.showComponent();
|
||||||
});
|
});
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
|
@ -732,11 +732,22 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
performAction(selectAction, getTreeActionContext(), true);
|
performAction(selectAction, getTreeActionContext(), true);
|
||||||
waitForTree();
|
waitForTree();
|
||||||
|
|
||||||
|
// NOTE: All nodes except the root node should be selected.
|
||||||
|
// Root is not selected to allow for most popup actions to
|
||||||
|
// be enabled and work as expected
|
||||||
|
|
||||||
BreadthFirstIterator it = new BreadthFirstIterator(rootNode);
|
BreadthFirstIterator it = new BreadthFirstIterator(rootNode);
|
||||||
|
int count = 0;
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
GTreeNode node = it.next();
|
GTreeNode node = it.next();
|
||||||
assertTrue(tree.isPathSelected(node.getTreePath()));
|
if (tree.isPathSelected(node.getTreePath())) {
|
||||||
|
++count;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
assertTrue(node.isRoot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(7, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -954,11 +965,12 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
for (TreePath path : paths) {
|
for (TreePath path : paths) {
|
||||||
|
|
||||||
GTreeNode node = (GTreeNode) path.getLastPathComponent();
|
GTreeNode node = (GTreeNode) path.getLastPathComponent();
|
||||||
if (node instanceof DomainFileNode) {
|
if (node instanceof DomainFileNode fileNode) {
|
||||||
fileList.add(((DomainFileNode) node).getDomainFile());
|
// NOTE: File may be a linked-folder. Treatment as folder or file depends on action
|
||||||
|
fileList.add(fileNode.getDomainFile());
|
||||||
}
|
}
|
||||||
else if (node instanceof DomainFolderNode) {
|
else if (node instanceof DomainFolderNode folderNode) {
|
||||||
folderList.add(((DomainFolderNode) node).getDomainFolder());
|
folderList.add(folderNode.getDomainFolder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.file.formats.android.oat.bundle;
|
package ghidra.file.formats.android.oat.bundle;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
|
@ -30,6 +29,7 @@ import ghidra.file.formats.android.oat.OatHeader;
|
||||||
import ghidra.file.formats.android.vdex.*;
|
import ghidra.file.formats.android.vdex.*;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.model.DomainFolder;
|
import ghidra.framework.model.DomainFolder;
|
||||||
|
import ghidra.program.database.ProgramContentHandler;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -45,8 +45,7 @@ public class FullOatBundle implements OatBundle {
|
||||||
|
|
||||||
private boolean isLittleEndian;
|
private boolean isLittleEndian;
|
||||||
|
|
||||||
FullOatBundle(Program oatProgram, OatHeader oatHeader, TaskMonitor monitor,
|
FullOatBundle(Program oatProgram, OatHeader oatHeader, TaskMonitor monitor, MessageLog log) {
|
||||||
MessageLog log) {
|
|
||||||
|
|
||||||
this.oatProgram = oatProgram;
|
this.oatProgram = oatProgram;
|
||||||
this.oatHeader = oatHeader;
|
this.oatHeader = oatHeader;
|
||||||
|
@ -120,12 +119,11 @@ public class FullOatBundle implements OatBundle {
|
||||||
DomainFolder parentFolder = domainFile.getParent();
|
DomainFolder parentFolder = domainFile.getParent();
|
||||||
|
|
||||||
//first, look in current project for VDEX file....
|
//first, look in current project for VDEX file....
|
||||||
if (lookInProjectFolder(HeaderType.VDEX, parentFolder,
|
if (lookInProjectFolder(HeaderType.VDEX, parentFolder, vdexProgramName, monitor, log)) {
|
||||||
vdexProgramName, monitor, log)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (lookInProjectFolder(HeaderType.VDEX, parentFolder.getParent(),
|
if (lookInProjectFolder(HeaderType.VDEX, parentFolder.getParent(), vdexProgramName, monitor,
|
||||||
vdexProgramName, monitor, log)) {
|
log)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +138,8 @@ public class FullOatBundle implements OatBundle {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
|
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
|
||||||
lookInProjectFolder(HeaderType.DEX, odexApkFolder, file.getName(),
|
lookInProjectFolder(HeaderType.DEX, odexApkFolder, file.getName(), monitor,
|
||||||
monitor, log);
|
log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,8 +151,8 @@ public class FullOatBundle implements OatBundle {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
|
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
|
||||||
lookInProjectFolder(HeaderType.DEX, apkOrJarFolder, file.getName(),
|
lookInProjectFolder(HeaderType.DEX, apkOrJarFolder, file.getName(), monitor,
|
||||||
monitor, log);
|
log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,8 +164,8 @@ public class FullOatBundle implements OatBundle {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (file.getName().startsWith(CDEX)) {
|
if (file.getName().startsWith(CDEX)) {
|
||||||
lookInProjectFolder(HeaderType.CDEX, appVdexFolder, file.getName(),
|
lookInProjectFolder(HeaderType.CDEX, appVdexFolder, file.getName(), monitor,
|
||||||
monitor, log);
|
log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,12 +181,11 @@ public class FullOatBundle implements OatBundle {
|
||||||
DomainFolder parentFolder = domainFile.getParent();
|
DomainFolder parentFolder = domainFile.getParent();
|
||||||
|
|
||||||
//first, look in current project for ART file....
|
//first, look in current project for ART file....
|
||||||
if (lookInProjectFolder(HeaderType.ART, parentFolder,
|
if (lookInProjectFolder(HeaderType.ART, parentFolder, artProgramName, monitor, log)) {
|
||||||
artProgramName, monitor, log)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (lookInProjectFolder(HeaderType.ART, parentFolder.getParent(),
|
if (lookInProjectFolder(HeaderType.ART, parentFolder.getParent(), artProgramName, monitor,
|
||||||
artProgramName, monitor, log)) {
|
log)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,14 +200,15 @@ public class FullOatBundle implements OatBundle {
|
||||||
* @param log the message log
|
* @param log the message log
|
||||||
*/
|
*/
|
||||||
private boolean lookInProjectFolder(HeaderType type, DomainFolder parentFolder,
|
private boolean lookInProjectFolder(HeaderType type, DomainFolder parentFolder,
|
||||||
String programName,
|
String programName, TaskMonitor monitor, MessageLog log) {
|
||||||
TaskMonitor monitor, MessageLog log) {
|
|
||||||
|
|
||||||
DomainFile child = parentFolder.getFile(programName);
|
DomainFile file = parentFolder.getFile(programName);
|
||||||
if (child != null) {
|
// Constrain to Program files only and not program link-files
|
||||||
|
if (file != null &&
|
||||||
|
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
||||||
Program program = null;
|
Program program = null;
|
||||||
try {
|
try {
|
||||||
program = (Program) child.getDomainObject(this, true, true, monitor);
|
program = (Program) file.getDomainObject(this, true, true, monitor);
|
||||||
ByteProvider provider =
|
ByteProvider provider =
|
||||||
MemoryByteProvider.createProgramHeaderByteProvider(program, false);
|
MemoryByteProvider.createProgramHeaderByteProvider(program, false);
|
||||||
return makeHeader(type, programName, provider, monitor);
|
return makeHeader(type, programName, provider, monitor);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.file.analyzers.FileFormatAnalyzer;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.database.ProgramContentHandler;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.PointerDataType;
|
import ghidra.program.model.data.PointerDataType;
|
||||||
import ghidra.program.model.lang.Processor;
|
import ghidra.program.model.lang.Processor;
|
||||||
|
@ -295,6 +296,7 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
|
||||||
|
|
||||||
private DestinationProgramInfo recurseFolder(DomainFolder folder, Address destinationAddress,
|
private DestinationProgramInfo recurseFolder(DomainFolder folder, Address destinationAddress,
|
||||||
ProgramManager programManager, TaskMonitor monitor) {
|
ProgramManager programManager, TaskMonitor monitor) {
|
||||||
|
// NOTE: All folder-links and file-links are ignored
|
||||||
DomainFolder[] folders = folder.getFolders();
|
DomainFolder[] folders = folder.getFolders();
|
||||||
for (DomainFolder child : folders) {
|
for (DomainFolder child : folders) {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
|
@ -311,15 +313,17 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
DomainObject domainObject = null;
|
if (!file.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Program program = null;
|
||||||
try {
|
try {
|
||||||
domainObject = file.getDomainObject(this, true /* upgrade */,
|
program = (Program) file.getDomainObject(this, true /* upgrade */,
|
||||||
false /* do not recover */, monitor);
|
false /* do not recover */, monitor);
|
||||||
if (domainObject instanceof Program) {
|
|
||||||
Program program = (Program) domainObject;
|
|
||||||
if (program.getMemory().contains(destinationAddress)) {
|
if (program.getMemory().contains(destinationAddress)) {
|
||||||
if (programManager != null) {
|
if (programManager != null) {
|
||||||
programManager.openProgram(program, ProgramManager.OPEN_VISIBLE);//once program is located, open it, so lookup is faster next time!
|
//once program is located, open it, so lookup is faster next time!
|
||||||
|
programManager.openProgram(program, ProgramManager.OPEN_VISIBLE);
|
||||||
}
|
}
|
||||||
SymbolTable symbolTable = program.getSymbolTable();
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
Symbol symbol = symbolTable.getPrimarySymbol(destinationAddress);
|
Symbol symbol = symbolTable.getPrimarySymbol(destinationAddress);
|
||||||
|
@ -328,13 +332,12 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
|
||||||
symbolName);
|
symbolName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.warn(this, e);
|
Msg.warn(this, e);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (domainObject != null) {
|
if (program != null) {
|
||||||
domainObject.release(this);
|
program.release(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ public class GFileSystemLoadKernelTask extends Task {
|
||||||
|
|
||||||
ProjectIndexService projectIndex = ProjectIndexService.getIndexFor(project);
|
ProjectIndexService projectIndex = ProjectIndexService.getIndexFor(project);
|
||||||
DomainFile existingDF = projectIndex.findFirstByFSRL(file.getFSRL());
|
DomainFile existingDF = projectIndex.findFirstByFSRL(file.getFSRL());
|
||||||
if ( existingDF != null && programManager != null ) {
|
if (existingDF != null && programManager != null) {
|
||||||
programManager.openProgram(existingDF);
|
programManager.openProgram(existingDF);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,9 @@ public class GFileSystemLoadKernelTask extends Task {
|
||||||
AppInfo.getActiveProject().getProjectData().getRootFolder(),
|
AppInfo.getActiveProject().getProjectData().getRootFolder(),
|
||||||
file.getParentFile().getPath());
|
file.getParentFile().getPath());
|
||||||
String fileName = ProjectDataUtils.getUniqueName(folder, program.getName());
|
String fileName = ProjectDataUtils.getUniqueName(folder, program.getName());
|
||||||
|
if (fileName == null) {
|
||||||
|
throw new IOException("Unable to find unique name for " + program.getName());
|
||||||
|
}
|
||||||
|
|
||||||
GhidraProgramUtilities.markProgramAnalyzed(program);
|
GhidraProgramUtilities.markProgramAnalyzed(program);
|
||||||
|
|
||||||
|
|
|
@ -250,12 +250,12 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkCompatibility(int serverInterfaceVersion) throws RemoteException {
|
public void checkCompatibility(int minServerInterfaceVersion) throws RemoteException {
|
||||||
if (serverInterfaceVersion > INTERFACE_VERSION) {
|
if (minServerInterfaceVersion > INTERFACE_VERSION) {
|
||||||
throw new RemoteException(
|
throw new RemoteException(
|
||||||
"Incompatible server interface, a newer Ghidra Server version is required.");
|
"Incompatible server interface, a newer Ghidra Server version is required.");
|
||||||
}
|
}
|
||||||
else if (serverInterfaceVersion < INTERFACE_VERSION) {
|
else if (minServerInterfaceVersion < MINIMUM_INTERFACE_VERSION) {
|
||||||
throw new RemoteException(
|
throw new RemoteException(
|
||||||
"Incompatible server interface, the minimum supported Ghidra version is " +
|
"Incompatible server interface, the minimum supported Ghidra version is " +
|
||||||
MIN_GHIDRA_VERSION);
|
MIN_GHIDRA_VERSION);
|
||||||
|
|
|
@ -381,6 +381,22 @@ public class RepositoryHandleImpl extends UnicastRemoteObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTextDataFile(String parentPath, String itemName, String fileID,
|
||||||
|
String contentType, String textData, String comment)
|
||||||
|
throws InvalidNameException, IOException {
|
||||||
|
synchronized (syncObject) {
|
||||||
|
validate();
|
||||||
|
repository.validateWritePrivilege(currentUser);
|
||||||
|
RepositoryFolder folder = repository.getFolder(currentUser, parentPath, true);
|
||||||
|
if (folder == null) {
|
||||||
|
throw new IOException("Failed to create repository Folder " + parentPath);
|
||||||
|
}
|
||||||
|
folder.createTextDataFile(itemName, fileID, contentType, textData, comment,
|
||||||
|
currentUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteManagedBufferFileHandle createDatabase(String parentPath, String itemName,
|
public RemoteManagedBufferFileHandle createDatabase(String parentPath, String itemName,
|
||||||
String fileID, int bufferSize, String contentType, String projectPath)
|
String fileID, int bufferSize, String contentType, String projectPath)
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class RepositoryFile {
|
||||||
private LocalFileSystem fileSystem;
|
private LocalFileSystem fileSystem;
|
||||||
private RepositoryFolder parent;
|
private RepositoryFolder parent;
|
||||||
private String name;
|
private String name;
|
||||||
private LocalDatabaseItem databaseItem;
|
private LocalFolderItem folderItem;
|
||||||
private RepositoryItem repositoryItem;
|
private RepositoryItem repositoryItem;
|
||||||
private boolean deleted = false;
|
private boolean deleted = false;
|
||||||
|
|
||||||
|
@ -69,11 +69,10 @@ public class RepositoryFile {
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
throw new FileNotFoundException(getPathname() + " not found");
|
throw new FileNotFoundException(getPathname() + " not found");
|
||||||
}
|
}
|
||||||
if (databaseItem == null) {
|
if (folderItem == null) {
|
||||||
repositoryItem = null;
|
repositoryItem = null;
|
||||||
LocalFolderItem folderItem = fileSystem.getItem(parent.getPathname(), name);
|
folderItem = fileSystem.getItem(parent.getPathname(), name);
|
||||||
if (folderItem == null || !folderItem.isVersioned() ||
|
if (folderItem == null) {
|
||||||
!(folderItem instanceof LocalDatabaseItem)) {
|
|
||||||
// must build pathname just in case folderItem does not exist
|
// must build pathname just in case folderItem does not exist
|
||||||
String pathname = parent.getPathname();
|
String pathname = parent.getPathname();
|
||||||
if (pathname.length() != 1) {
|
if (pathname.length() != 1) {
|
||||||
|
@ -84,7 +83,6 @@ public class RepositoryFile {
|
||||||
"file is corrupt or unsupported", null);
|
"file is corrupt or unsupported", null);
|
||||||
throw new FileNotFoundException(pathname + " is corrupt or unsupported");
|
throw new FileNotFoundException(pathname + " is corrupt or unsupported");
|
||||||
}
|
}
|
||||||
this.databaseItem = (LocalDatabaseItem) folderItem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,16 +125,33 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
try {
|
try {
|
||||||
validate();
|
validate();
|
||||||
if (repositoryItem == null) {
|
if (repositoryItem == null && folderItem != null) {
|
||||||
repositoryItem =
|
String textData = null;
|
||||||
new RepositoryItem(parent.getPathname(), name, databaseItem.getFileID(),
|
int itemType = -1;
|
||||||
RepositoryItem.DATABASE, databaseItem.getContentType(),
|
if (folderItem instanceof DatabaseItem) {
|
||||||
databaseItem.getCurrentVersion(), databaseItem.lastModified());
|
itemType = RepositoryItem.DATABASE;
|
||||||
|
}
|
||||||
|
else if (folderItem instanceof TextDataItem textItem) {
|
||||||
|
itemType = RepositoryItem.TEXT_DATA_FILE;
|
||||||
|
textData = textItem.getTextData();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
repository.log(getPathname(),
|
||||||
|
"Unsupported item type: " + folderItem.getClass().getSimpleName(),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
repositoryItem = new RepositoryItem(parent.getPathname(), name,
|
||||||
|
folderItem.getFileID(), itemType, folderItem.getContentType(),
|
||||||
|
folderItem.getCurrentVersion(), folderItem.lastModified(), textData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
repository.log(getPathname(), "Item failure: " + e.getMessage(), null);
|
||||||
|
}
|
||||||
|
if (repository == null) {
|
||||||
repositoryItem = new RepositoryItem(parent.getPathname(), name, null,
|
repositoryItem = new RepositoryItem(parent.getPathname(), name, null,
|
||||||
RepositoryItem.DATABASE, "INVALID", 0, 0);
|
RepositoryItem.FILE, "INVALID", 0, 0, null);
|
||||||
}
|
}
|
||||||
return repositoryItem;
|
return repositoryItem;
|
||||||
}
|
}
|
||||||
|
@ -157,9 +172,14 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
repository.validateReadPrivilege(user);
|
repository.validateReadPrivilege(user);
|
||||||
|
if (!(folderItem instanceof LocalDatabaseItem databaseItem)) {
|
||||||
|
throw new IOException(
|
||||||
|
"Unsupported operation for " + folderItem.getClass().getSimpleName());
|
||||||
|
}
|
||||||
LocalManagedBufferFile bf = databaseItem.open(version, minChangeDataVer);
|
LocalManagedBufferFile bf = databaseItem.open(version, minChangeDataVer);
|
||||||
repository.log(getPathname(), "version " +
|
repository.log(
|
||||||
(version < 0 ? databaseItem.getCurrentVersion() : version) + " opened read-only",
|
getPathname(), "version " +
|
||||||
|
(version < 0 ? folderItem.getCurrentVersion() : version) + " opened read-only",
|
||||||
user);
|
user);
|
||||||
return bf;
|
return bf;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +197,11 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
repository.validateWritePrivilege(user);
|
repository.validateWritePrivilege(user);
|
||||||
ItemCheckoutStatus coStatus = databaseItem.getCheckout(checkoutId);
|
if (!(folderItem instanceof LocalDatabaseItem databaseItem)) {
|
||||||
|
throw new IOException(
|
||||||
|
"Unsupported operation for " + folderItem.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
ItemCheckoutStatus coStatus = folderItem.getCheckout(checkoutId);
|
||||||
if (coStatus == null) {
|
if (coStatus == null) {
|
||||||
throw new IOException("Illegal checkin");
|
throw new IOException("Illegal checkin");
|
||||||
}
|
}
|
||||||
|
@ -202,7 +226,7 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
repository.validateReadPrivilege(user);
|
repository.validateReadPrivilege(user);
|
||||||
return databaseItem.getVersions();
|
return folderItem.getVersions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +240,7 @@ public class RepositoryFile {
|
||||||
public long length() throws IOException {
|
public long length() throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
return databaseItem.length();
|
return folderItem.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +258,7 @@ public class RepositoryFile {
|
||||||
User userObj = repository.validateWritePrivilege(user);
|
User userObj = repository.validateWritePrivilege(user);
|
||||||
|
|
||||||
if (!userObj.isAdmin()) {
|
if (!userObj.isAdmin()) {
|
||||||
Version[] versions = databaseItem.getVersions();
|
Version[] versions = folderItem.getVersions();
|
||||||
if (deleteVersion == -1) {
|
if (deleteVersion == -1) {
|
||||||
for (Version version : versions) {
|
for (Version version : versions) {
|
||||||
if (!user.equals(version.getUser())) {
|
if (!user.equals(version.getUser())) {
|
||||||
|
@ -259,21 +283,13 @@ public class RepositoryFile {
|
||||||
throw new IOException("Only the oldest or latest version may be deleted");
|
throw new IOException("Only the oldest or latest version may be deleted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String oldPath = getPathname();
|
|
||||||
if (databaseItem == null) {
|
|
||||||
// forced removal by repo Admin
|
|
||||||
|
|
||||||
}
|
if (folderItem != null) {
|
||||||
else {
|
folderItem.delete(deleteVersion, user);
|
||||||
databaseItem.delete(deleteVersion, user);
|
|
||||||
}
|
}
|
||||||
deleted = true;
|
deleted = true;
|
||||||
repositoryItem = null;
|
repositoryItem = null;
|
||||||
parent.fileDeleted(this);
|
parent.fileDeleted(this);
|
||||||
RepositoryFile newRf = parent.getFile(name);
|
|
||||||
if (newRf == null) {
|
|
||||||
RepositoryManager.log(repository.getName(), oldPath, "file deleted", user);
|
|
||||||
}
|
|
||||||
parent = null;
|
parent = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +336,7 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
repository.validateWritePrivilege(user); // don't allow checkout if read-only
|
repository.validateWritePrivilege(user); // don't allow checkout if read-only
|
||||||
ItemCheckoutStatus coStatus = databaseItem.checkout(checkoutType, user, projectPath);
|
ItemCheckoutStatus coStatus = folderItem.checkout(checkoutType, user, projectPath);
|
||||||
if (coStatus != null && checkoutType != CheckoutType.NORMAL && repositoryItem != null &&
|
if (coStatus != null && checkoutType != CheckoutType.NORMAL && repositoryItem != null &&
|
||||||
repositoryItem.getFileID() == null) {
|
repositoryItem.getFileID() == null) {
|
||||||
repositoryItem = null; // force refresh since fileID should get reset
|
repositoryItem = null; // force refresh since fileID should get reset
|
||||||
|
@ -340,7 +356,7 @@ public class RepositoryFile {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
databaseItem.updateCheckoutVersion(checkoutId, checkoutVersion, user);
|
folderItem.updateCheckoutVersion(checkoutId, checkoutVersion, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,14 +370,14 @@ public class RepositoryFile {
|
||||||
public void terminateCheckout(long checkoutId, String user, boolean notify) throws IOException {
|
public void terminateCheckout(long checkoutId, String user, boolean notify) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
ItemCheckoutStatus coStatus = databaseItem.getCheckout(checkoutId);
|
ItemCheckoutStatus coStatus = folderItem.getCheckout(checkoutId);
|
||||||
if (coStatus != null) {
|
if (coStatus != null) {
|
||||||
User userObj = repository.getUser(user);
|
User userObj = repository.getUser(user);
|
||||||
if (!userObj.isAdmin() && !coStatus.getUser().equals(user)) {
|
if (!userObj.isAdmin() && !coStatus.getUser().equals(user)) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Undo-checkout not permitted, checkout was made by " + coStatus.getUser());
|
"Undo-checkout not permitted, checkout was made by " + coStatus.getUser());
|
||||||
}
|
}
|
||||||
databaseItem.terminateCheckout(checkoutId, notify);
|
folderItem.terminateCheckout(checkoutId, notify);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,7 +394,7 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
repository.validateReadPrivilege(user);
|
repository.validateReadPrivilege(user);
|
||||||
return databaseItem.getCheckout(checkoutId);
|
return folderItem.getCheckout(checkoutId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +409,7 @@ public class RepositoryFile {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
repository.validateReadPrivilege(user);
|
repository.validateReadPrivilege(user);
|
||||||
return databaseItem.getCheckouts();
|
return folderItem.getCheckouts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,7 +421,7 @@ public class RepositoryFile {
|
||||||
public boolean hasCheckouts() throws IOException {
|
public boolean hasCheckouts() throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
return databaseItem.hasCheckouts();
|
return folderItem.hasCheckouts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +433,7 @@ public class RepositoryFile {
|
||||||
public boolean isCheckinActive() throws IOException {
|
public boolean isCheckinActive() throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
validate();
|
validate();
|
||||||
return databaseItem.isCheckinActive();
|
return folderItem.isCheckinActive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,7 +452,7 @@ public class RepositoryFile {
|
||||||
void pathChanged() {
|
void pathChanged() {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
repositoryItem = null;
|
repositoryItem = null;
|
||||||
databaseItem = null;
|
folderItem = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,7 @@ import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import db.buffers.LocalManagedBufferFile;
|
import db.buffers.LocalManagedBufferFile;
|
||||||
import ghidra.framework.store.*;
|
import ghidra.framework.store.*;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.*;
|
||||||
import ghidra.framework.store.local.LocalFolderItem;
|
|
||||||
import ghidra.server.Repository;
|
import ghidra.server.Repository;
|
||||||
import ghidra.server.RepositoryManager;
|
import ghidra.server.RepositoryManager;
|
||||||
import ghidra.util.InvalidNameException;
|
import ghidra.util.InvalidNameException;
|
||||||
|
@ -94,20 +93,20 @@ public class RepositoryFolder {
|
||||||
private void init() throws IOException {
|
private void init() throws IOException {
|
||||||
String path = getPathname();
|
String path = getPathname();
|
||||||
String[] names = fileSystem.getFolderNames(path);
|
String[] names = fileSystem.getFolderNames(path);
|
||||||
for (String name2 : names) {
|
for (String folderName : names) {
|
||||||
RepositoryFolder subfolder = new RepositoryFolder(repository, fileSystem, this, name2);
|
RepositoryFolder subfolder =
|
||||||
folderMap.put(name2, subfolder);
|
new RepositoryFolder(repository, fileSystem, this, folderName);
|
||||||
|
folderMap.put(folderName, subfolder);
|
||||||
}
|
}
|
||||||
names = fileSystem.getItemNames(path);
|
names = fileSystem.getItemNames(path);
|
||||||
int badItemCount = 0;
|
int badItemCount = 0;
|
||||||
for (String name2 : names) {
|
for (String itemName : names) {
|
||||||
LocalFolderItem item = fileSystem.getItem(path, name2);
|
LocalFolderItem item = fileSystem.getItem(path, itemName);
|
||||||
if (item == null || !(item instanceof DatabaseItem)) {
|
if (item == null || (item instanceof UnknownFolderItem)) {
|
||||||
++badItemCount;
|
++badItemCount;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, name2);
|
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, itemName);
|
||||||
fileMap.put(name2, rf);
|
fileMap.put(itemName, rf);
|
||||||
}
|
}
|
||||||
if (badItemCount != 0) {
|
if (badItemCount != 0) {
|
||||||
log.error("Repository '" + repository.getName() + "' contains " + badItemCount +
|
log.error("Repository '" + repository.getName() + "' contains " + badItemCount +
|
||||||
|
@ -217,7 +216,7 @@ public class RepositoryFolder {
|
||||||
if (fileSystem.fileExists(getPathname(), fileName)) {
|
if (fileSystem.fileExists(getPathname(), fileName)) {
|
||||||
try {
|
try {
|
||||||
LocalFolderItem item = fileSystem.getItem(getPathname(), fileName);
|
LocalFolderItem item = fileSystem.getItem(getPathname(), fileName);
|
||||||
if (item == null || !(item instanceof DatabaseItem)) {
|
if (item == null) {
|
||||||
log.error("Repository '" + repository.getName() + "' contains bad item: " +
|
log.error("Repository '" + repository.getName() + "' contains bad item: " +
|
||||||
makePathname(getPathname(), fileName));
|
makePathname(getPathname(), fileName));
|
||||||
return null;
|
return null;
|
||||||
|
@ -262,6 +261,41 @@ public class RepositoryFolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new text data file within the specified parent folder.
|
||||||
|
* @param itemName new data file name
|
||||||
|
* @param fileID file ID to be associated with new file or null
|
||||||
|
* @param contentType application defined content type
|
||||||
|
* @param textData text data (required)
|
||||||
|
* @param comment file comment (may be null)
|
||||||
|
* @param user user who is initiating request
|
||||||
|
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||||
|
* @throws InvalidNameException if the name has illegal characters.
|
||||||
|
* @throws IOException if an IO error occurs.
|
||||||
|
*/
|
||||||
|
public void createTextDataFile(String itemName, String fileID, String contentType,
|
||||||
|
String textData, String comment, String user) throws InvalidNameException, IOException {
|
||||||
|
synchronized (fileSystem) {
|
||||||
|
repository.validate();
|
||||||
|
repository.validateWritePrivilege(user);
|
||||||
|
if (getFile(itemName) != null) {
|
||||||
|
throw new DuplicateFileException(itemName + " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalTextDataItem textDataItem = fileSystem.createTextDataItem(getPathname(), itemName,
|
||||||
|
fileID, contentType, textData, null); // comment conveyed with Version info below
|
||||||
|
|
||||||
|
Version singleVersion = new Version(1, System.currentTimeMillis(), user, comment);
|
||||||
|
textDataItem.setVersionInfo(singleVersion);
|
||||||
|
|
||||||
|
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, itemName);
|
||||||
|
fileMap.put(itemName, rf);
|
||||||
|
|
||||||
|
RepositoryManager.log(repository.getName(), makePathname(getPathname(), itemName),
|
||||||
|
"file created", user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new database file/item within this folder.
|
* Create a new database file/item within this folder.
|
||||||
* @param itemName name of new database
|
* @param itemName name of new database
|
||||||
|
@ -445,4 +479,5 @@ public class RepositoryFolder {
|
||||||
: parentPath;
|
: parentPath;
|
||||||
return path + FileSystem.SEPARATOR + childName;
|
return path + FileSystem.SEPARATOR + childName;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
//
|
//
|
||||||
//@category Version Tracking
|
//@category Version Tracking
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.feature.vt.api.db.VTSessionContentHandler;
|
||||||
import ghidra.feature.vt.api.db.VTSessionDB;
|
import ghidra.feature.vt.api.db.VTSessionDB;
|
||||||
import ghidra.feature.vt.api.main.VTSession;
|
import ghidra.feature.vt.api.main.VTSession;
|
||||||
import ghidra.feature.vt.api.util.VTOptions;
|
import ghidra.feature.vt.api.util.VTOptions;
|
||||||
|
@ -108,7 +109,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Exception {
|
public void run() throws Exception {
|
||||||
|
|
||||||
if(currentProgram == null) {
|
if (currentProgram == null) {
|
||||||
println("Please open the destination program.");
|
println("Please open the destination program.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -175,8 +176,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Program sourceProgram = (Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded,
|
Program sourceProgram =
|
||||||
false, monitor);
|
(Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded, false, monitor);
|
||||||
|
|
||||||
VTSession session = null;
|
VTSession session = null;
|
||||||
try {
|
try {
|
||||||
|
@ -258,19 +259,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
* @throws CancelledException if cancelled
|
* @throws CancelledException if cancelled
|
||||||
*/
|
*/
|
||||||
private boolean hasExistingSession(String name, DomainFolder folder) throws CancelledException {
|
private boolean hasExistingSession(String name, DomainFolder folder) throws CancelledException {
|
||||||
|
DomainFile file = folder.getFile(name);
|
||||||
DomainFile[] files = folder.getFiles();
|
return file != null && file.getContentType().equals(VTSessionContentHandler.CONTENT_TYPE);
|
||||||
|
|
||||||
for (DomainFile file : files) {
|
|
||||||
monitor.checkCancelled();
|
|
||||||
|
|
||||||
if (file.getName().equals(name)) {
|
|
||||||
if (file.getContentType().equals("VersionTracking")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -333,7 +333,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), type, "open",
|
VersionExceptionHandler.showVersionError(null, domainFile.getName(), type, "open",
|
||||||
e);
|
false, e);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.showError(this, null, "Can't open " + type + ": " + domainFile.getName(),
|
Msg.showError(this, null, "Can't open " + type + ": " + domainFile.getName(),
|
||||||
|
|
|
@ -25,8 +25,8 @@ import ghidra.feature.vt.api.main.VTSession;
|
||||||
import ghidra.feature.vt.gui.plugin.VTController;
|
import ghidra.feature.vt.gui.plugin.VTController;
|
||||||
import ghidra.feature.vt.gui.plugin.VTPlugin;
|
import ghidra.feature.vt.gui.plugin.VTPlugin;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.DataTreeDialog;
|
||||||
|
import ghidra.framework.model.DefaultDomainFileFilter;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.model.DomainFileFilter;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ public class OpenVersionTrackingSessionAction extends DockingAction {
|
||||||
PluginTool tool = controller.getTool();
|
PluginTool tool = controller.getTool();
|
||||||
DataTreeDialog dialog =
|
DataTreeDialog dialog =
|
||||||
new DataTreeDialog(tool.getToolFrame(), "Open Version Tracking Session", OPEN,
|
new DataTreeDialog(tool.getToolFrame(), "Open Version Tracking Session", OPEN,
|
||||||
new VTDomainFileFilter());
|
new DefaultDomainFileFilter(VTSession.class, true));
|
||||||
|
|
||||||
tool.showDialog(dialog);
|
tool.showDialog(dialog);
|
||||||
if (!dialog.wasCancelled()) {
|
if (!dialog.wasCancelled()) {
|
||||||
|
@ -57,16 +57,4 @@ public class OpenVersionTrackingSessionAction extends DockingAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VTDomainFileFilter implements DomainFileFilter {
|
|
||||||
@Override
|
|
||||||
public boolean accept(DomainFile f) {
|
|
||||||
Class<?> c = f.getDomainObjectClass();
|
|
||||||
return VTSession.class.isAssignableFrom(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean followLinkedFolders() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,7 @@ public class VTControllerImpl
|
||||||
}
|
}
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), "VT Session",
|
VersionExceptionHandler.showVersionError(null, domainFile.getName(), "VT Session",
|
||||||
"open", e);
|
"open", false, e);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.showError(this, null, "Can't open VT Session: " + domainFile.getName(),
|
Msg.showError(this, null, "Can't open VT Session: " + domainFile.getName(),
|
||||||
|
|
|
@ -31,8 +31,7 @@ import generic.theme.GIcon;
|
||||||
import generic.theme.GThemeDefaults.Ids.Fonts;
|
import generic.theme.GThemeDefaults.Ids.Fonts;
|
||||||
import generic.theme.Gui;
|
import generic.theme.Gui;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.DataTreeDialog;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.model.DomainFolder;
|
|
||||||
import ghidra.util.StringUtilities;
|
import ghidra.util.StringUtilities;
|
||||||
import utility.function.Callback;
|
import utility.function.Callback;
|
||||||
|
|
||||||
|
@ -230,8 +229,8 @@ public class SessionConfigurationPanel extends JPanel {
|
||||||
JButton button = new BrowseButton();
|
JButton button = new BrowseButton();
|
||||||
button.setName("SOURCE_BUTTON");
|
button.setName("SOURCE_BUTTON");
|
||||||
button.addActionListener(e -> {
|
button.addActionListener(e -> {
|
||||||
DomainFile programFile = VTWizardUtils.chooseDomainFile(SessionConfigurationPanel.this,
|
DomainFile programFile = VTWizardUtils.chooseProgramFile(SessionConfigurationPanel.this,
|
||||||
"a source program", VTWizardUtils.PROGRAM_FILTER, null);
|
"a source program", null);
|
||||||
if (programFile != null) {
|
if (programFile != null) {
|
||||||
setSourceFile(programFile);
|
setSourceFile(programFile);
|
||||||
statusChangedCallback.call();
|
statusChangedCallback.call();
|
||||||
|
@ -244,8 +243,8 @@ public class SessionConfigurationPanel extends JPanel {
|
||||||
JButton button = new BrowseButton();
|
JButton button = new BrowseButton();
|
||||||
button.setName("DESTINATION_BUTTON");
|
button.setName("DESTINATION_BUTTON");
|
||||||
button.addActionListener(e -> {
|
button.addActionListener(e -> {
|
||||||
DomainFile programFile = VTWizardUtils.chooseDomainFile(SessionConfigurationPanel.this,
|
DomainFile programFile = VTWizardUtils.chooseProgramFile(SessionConfigurationPanel.this,
|
||||||
"a destination program", VTWizardUtils.PROGRAM_FILTER, null);
|
"a destination program", null);
|
||||||
if (programFile != null) {
|
if (programFile != null) {
|
||||||
setDestinationFile(programFile);
|
setDestinationFile(programFile);
|
||||||
statusChangedCallback.call();
|
statusChangedCallback.call();
|
||||||
|
|
|
@ -25,8 +25,7 @@ import docking.widgets.OptionDialog;
|
||||||
import ghidra.feature.vt.api.main.VTSession;
|
import ghidra.feature.vt.api.main.VTSession;
|
||||||
import ghidra.feature.vt.gui.task.SaveTask;
|
import ghidra.feature.vt.gui.task.SaveTask;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.DataTreeDialog;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.model.DomainFileFilter;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
import ghidra.util.task.TaskLauncher;
|
import ghidra.util.task.TaskLauncher;
|
||||||
|
@ -37,29 +36,13 @@ public class VTWizardUtils {
|
||||||
DomainFile df;
|
DomainFile df;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final DomainFileFilter VT_SESSION_FILTER = new DomainFileFilter() {
|
public static final DomainFileFilter VT_SESSION_FILTER =
|
||||||
|
new DefaultDomainFileFilter(VTSession.class, true);
|
||||||
|
|
||||||
@Override
|
public static DomainFile chooseProgramFile(Component parent, String domainIdentifier,
|
||||||
public boolean accept(DomainFile df) {
|
DomainFile fileToSelect) {
|
||||||
return VTSession.class.isAssignableFrom(df.getDomainObjectClass());
|
final DataTreeDialog dataTreeDialog = new DataTreeDialog(parent,
|
||||||
}
|
"Choose " + domainIdentifier, OPEN, new DefaultDomainFileFilter(Program.class, true));
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean followLinkedFolders() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final DomainFileFilter PROGRAM_FILTER = f -> {
|
|
||||||
return Program.class.isAssignableFrom(f.getDomainObjectClass());
|
|
||||||
};
|
|
||||||
|
|
||||||
public static DomainFile chooseDomainFile(Component parent, String domainIdentifier,
|
|
||||||
DomainFileFilter filter, DomainFile fileToSelect) {
|
|
||||||
final DataTreeDialog dataTreeDialog = filter == null
|
|
||||||
? new DataTreeDialog(parent, "Choose " + domainIdentifier, OPEN)
|
|
||||||
: new DataTreeDialog(parent, "Choose " + domainIdentifier, OPEN,
|
|
||||||
filter);
|
|
||||||
final DomainFileBox box = new DomainFileBox();
|
final DomainFileBox box = new DomainFileBox();
|
||||||
dataTreeDialog.addOkActionListener(new ActionListener() {
|
dataTreeDialog.addOkActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -34,7 +34,7 @@ import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.model.mem.StubMemory;
|
import ghidra.program.model.mem.StubMemory;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
|
|
||||||
public class VTBaseTestCase extends AbstractGenericTest {
|
public abstract class VTBaseTestCase extends AbstractGenericTest {
|
||||||
|
|
||||||
private DomainFile sourceDomainFile = new TestDummyDomainFile(null, "SourceDomainFile") {
|
private DomainFile sourceDomainFile = new TestDummyDomainFile(null, "SourceDomainFile") {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -448,7 +448,14 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
||||||
int count = 1;
|
int count = 1;
|
||||||
for (GTreeNode child : children) {
|
for (GTreeNode child : children) {
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
|
if (child.isAutoExpandPermitted()) {
|
||||||
|
// count the child and its children
|
||||||
count += child.loadAll(monitor);
|
count += child.loadAll(monitor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// count just the child
|
||||||
|
++count;
|
||||||
|
}
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
|
|
|
@ -22,6 +22,9 @@ import docking.widgets.tree.GTreeNode;
|
||||||
/**
|
/**
|
||||||
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
|
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
|
||||||
* return in breadth first order.
|
* return in breadth first order.
|
||||||
|
* <br>
|
||||||
|
* NOTE: Iterator will not include children of a node where {@link GTreeNode#isAutoExpandPermitted()}
|
||||||
|
* returns false.
|
||||||
*/
|
*/
|
||||||
public class BreadthFirstIterator implements Iterator<GTreeNode> {
|
public class BreadthFirstIterator implements Iterator<GTreeNode> {
|
||||||
private Queue<GTreeNode> nodeQueue = new LinkedList<GTreeNode>();
|
private Queue<GTreeNode> nodeQueue = new LinkedList<GTreeNode>();
|
||||||
|
@ -39,7 +42,7 @@ public class BreadthFirstIterator implements Iterator<GTreeNode> {
|
||||||
@Override
|
@Override
|
||||||
public GTreeNode next() {
|
public GTreeNode next() {
|
||||||
lastNode = nodeQueue.poll();
|
lastNode = nodeQueue.poll();
|
||||||
if (lastNode != null) {
|
if (lastNode != null && lastNode.isAutoExpandPermitted()) {
|
||||||
List<GTreeNode> children = lastNode.getChildren();
|
List<GTreeNode> children = lastNode.getChildren();
|
||||||
nodeQueue.addAll(children);
|
nodeQueue.addAll(children);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ import docking.widgets.tree.GTreeNode;
|
||||||
/**
|
/**
|
||||||
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
|
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
|
||||||
* return in depth first order.
|
* return in depth first order.
|
||||||
|
* <br>
|
||||||
|
* NOTE: Iterator will not include children of a node where {@link GTreeNode#isAutoExpandPermitted()}
|
||||||
|
* returns false.
|
||||||
*/
|
*/
|
||||||
public class DepthFirstIterator implements Iterator<GTreeNode> {
|
public class DepthFirstIterator implements Iterator<GTreeNode> {
|
||||||
private Stack<Iterator<GTreeNode>> stack = new Stack<>();
|
private Stack<Iterator<GTreeNode>> stack = new Stack<>();
|
||||||
|
@ -49,7 +52,7 @@ public class DepthFirstIterator implements Iterator<GTreeNode> {
|
||||||
it = stack.pop();
|
it = stack.pop();
|
||||||
}
|
}
|
||||||
lastNode = it.next();
|
lastNode = it.next();
|
||||||
if (lastNode.getChildCount() > 0) {
|
if (lastNode.isAutoExpandPermitted() && lastNode.getChildCount() > 0) {
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
stack.push(it);
|
stack.push(it);
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if connection recently was lost unexpectedly
|
* Returns true if connection recently was lost unexpectedly
|
||||||
|
* @return true if connection recently was lost unexpectedly
|
||||||
*/
|
*/
|
||||||
public boolean hadUnexpectedDisconnect() {
|
public boolean hadUnexpectedDisconnect() {
|
||||||
return unexpectedDisconnect;
|
return unexpectedDisconnect;
|
||||||
|
@ -151,6 +152,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if connected.
|
* Returns true if connected.
|
||||||
|
* @return true if connected.
|
||||||
*/
|
*/
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return repository != null;
|
return repository != null;
|
||||||
|
@ -177,7 +179,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
if (repository == null) {
|
if (repository == null) {
|
||||||
serverAdapter.connect(); // may cause auto-reconnect of repository
|
serverAdapter.connect(); // may cause auto-reconnect of repository
|
||||||
}
|
}
|
||||||
if (repository == null) {
|
if (repository == null && serverAdapter.isConnected()) {
|
||||||
repository = serverAdapter.getRepositoryHandle(name);
|
repository = serverAdapter.getRepositoryHandle(name);
|
||||||
unexpectedDisconnect = false;
|
unexpectedDisconnect = false;
|
||||||
if (repository == null) {
|
if (repository == null) {
|
||||||
|
@ -193,8 +195,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event reader for change dispatcher.
|
* Event reader for change dispatcher.
|
||||||
* @return
|
* @return events
|
||||||
* @throws IOException
|
|
||||||
* @throws InterruptedIOException if repository handle is closed
|
* @throws InterruptedIOException if repository handle is closed
|
||||||
*/
|
*/
|
||||||
RepositoryChangeEvent[] getEvents() throws InterruptedIOException {
|
RepositoryChangeEvent[] getEvents() throws InterruptedIOException {
|
||||||
|
@ -227,21 +228,24 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns repository name
|
* Get the associated repository name
|
||||||
|
* @return repository name
|
||||||
*/
|
*/
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns server adapter
|
* Get the associated server adapter
|
||||||
|
* @return server adapter
|
||||||
*/
|
*/
|
||||||
public RepositoryServerAdapter getServer() {
|
public RepositoryServerAdapter getServer() {
|
||||||
return serverAdapter;
|
return serverAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns server information
|
* Returns associated server information
|
||||||
|
* @return server information
|
||||||
*/
|
*/
|
||||||
public ServerInfo getServerInfo() {
|
public ServerInfo getServerInfo() {
|
||||||
return serverAdapter.getServerInfo();
|
return serverAdapter.getServerInfo();
|
||||||
|
@ -280,9 +284,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns repository user object.
|
* Returns repository connected user object.
|
||||||
|
* @return connected user object
|
||||||
* @throws UserAccessException user no longer has any permission to use repository.
|
* @throws UserAccessException user no longer has any permission to use repository.
|
||||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||||
|
* @throws IOException if an IO error occurs
|
||||||
* @see ghidra.framework.remote.RemoteRepositoryHandle#getUser()
|
* @see ghidra.framework.remote.RemoteRepositoryHandle#getUser()
|
||||||
*/
|
*/
|
||||||
public User getUser() throws IOException {
|
public User getUser() throws IOException {
|
||||||
|
@ -305,7 +311,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if anonymous access allowed by this repository
|
* @return true if anonymous access allowed by this repository
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
public boolean anonymousAccessAllowed() throws IOException {
|
public boolean anonymousAccessAllowed() throws IOException {
|
||||||
synchronized (serverAdapter) {
|
synchronized (serverAdapter) {
|
||||||
|
@ -323,10 +329,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list of repository users.
|
* Returns list of repository users with repository access permission
|
||||||
* @throws IOException
|
* @return return users with repository access permission
|
||||||
* @throws UserAccessException user no longer has any permission to use repository.
|
* @throws UserAccessException user no longer has any permission to use repository.
|
||||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||||
|
* @throws IOException if an IO error occurs
|
||||||
* @see RemoteRepositoryHandle#getUserList()
|
* @see RemoteRepositoryHandle#getUserList()
|
||||||
*/
|
*/
|
||||||
public User[] getUserList() throws IOException {
|
public User[] getUserList() throws IOException {
|
||||||
|
@ -345,10 +352,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list of all users known to server.
|
* Returns list of all user names known to server.
|
||||||
* @throws IOException
|
* @return list of all user names known to server.
|
||||||
* @throws UserAccessException user no longer has any permission to use repository.
|
* @throws UserAccessException user no longer has any permission to use repository.
|
||||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||||
|
* @throws IOException if an IO error occurs
|
||||||
* @see RemoteRepositoryHandle#getServerUserList()
|
* @see RemoteRepositoryHandle#getServerUserList()
|
||||||
*/
|
*/
|
||||||
public String[] getServerUserList() throws IOException {
|
public String[] getServerUserList() throws IOException {
|
||||||
|
@ -371,8 +379,8 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
* @param users list of user and access permissions.
|
* @param users list of user and access permissions.
|
||||||
* @param anonymousAccessAllowed true to permit anonymous access (also requires anonymous
|
* @param anonymousAccessAllowed true to permit anonymous access (also requires anonymous
|
||||||
* access to be enabled for server)
|
* access to be enabled for server)
|
||||||
* @throws UserAccessException
|
* @throws UserAccessException user is not a repository Admin
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||||
* @see RemoteRepositoryHandle#setUserList(User[], boolean)
|
* @see RemoteRepositoryHandle#setUserList(User[], boolean)
|
||||||
*/
|
*/
|
||||||
|
@ -392,7 +400,36 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
|
* @see RepositoryHandle#createTextDataFile(String, String, String, String, String, String)
|
||||||
|
*/
|
||||||
|
public void createTextDataFile(String parentPath, String itemName, String fileID,
|
||||||
|
String contentType, String textData, String comment)
|
||||||
|
throws IOException, InvalidNameException {
|
||||||
|
synchronized (serverAdapter) {
|
||||||
|
checkRepository();
|
||||||
|
try {
|
||||||
|
repository.createTextDataFile(parentPath, itemName, fileID, contentType, textData,
|
||||||
|
comment);
|
||||||
|
}
|
||||||
|
catch (NotConnectedException | RemoteException e) {
|
||||||
|
checkUnmarshalException(e, "createTextDataFile");
|
||||||
|
if (recoverConnection(e)) {
|
||||||
|
try {
|
||||||
|
repository.createTextDataFile(parentPath, itemName, fileID, contentType,
|
||||||
|
textData, comment);
|
||||||
|
}
|
||||||
|
catch (RemoteException e1) {
|
||||||
|
checkUnmarshalException(e1, "createTextDataFile");
|
||||||
|
throw e1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
* @see RepositoryHandle#createDatabase(String, String, String, int, String, String)
|
* @see RepositoryHandle#createDatabase(String, String, String, int, String, String)
|
||||||
*/
|
*/
|
||||||
public ManagedBufferFileAdapter createDatabase(String parentPath, String itemName,
|
public ManagedBufferFileAdapter createDatabase(String parentPath, String itemName,
|
||||||
|
@ -530,8 +567,8 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert UnmarshalException into UnsupportedOperationException
|
* Convert UnmarshalException into UnsupportedOperationException
|
||||||
* @param e
|
* @param e IOException to be converted if appropriate
|
||||||
* @throws UnsupportedOperationException
|
* @throws UnsupportedOperationException unsupported operation exception
|
||||||
*/
|
*/
|
||||||
private void checkUnmarshalException(IOException e, String operation)
|
private void checkUnmarshalException(IOException e, String operation)
|
||||||
throws UnsupportedOperationException {
|
throws UnsupportedOperationException {
|
||||||
|
@ -931,5 +968,4 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
||||||
public int getOpenFileHandleCount() {
|
public int getOpenFileHandleCount() {
|
||||||
return openFileHandleCount;
|
return openFileHandleCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,13 +168,14 @@ class ServerConnectTask extends Task {
|
||||||
monitor.setCancelEnabled(false);
|
monitor.setCancelEnabled(false);
|
||||||
monitor.setMessage("Connecting...");
|
monitor.setMessage("Connecting...");
|
||||||
|
|
||||||
Registry reg =
|
Registry reg = LocateRegistry.getRegistry(server.getServerName(),
|
||||||
LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
|
server.getPortNumber(), new SslRMIClientSocketFactory());
|
||||||
new SslRMIClientSocketFactory());
|
|
||||||
checkServerBindNames(reg);
|
checkServerBindNames(reg);
|
||||||
|
|
||||||
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
|
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
|
||||||
gsh.checkCompatibility(GhidraServerHandle.INTERFACE_VERSION);
|
|
||||||
|
// Check interface compatibility with the minimum supported version
|
||||||
|
gsh.checkCompatibility(GhidraServerHandle.MINIMUM_INTERFACE_VERSION);
|
||||||
}
|
}
|
||||||
catch (NotBoundException e) {
|
catch (NotBoundException e) {
|
||||||
throw new IOException(e.getMessage());
|
throw new IOException(e.getMessage());
|
||||||
|
@ -237,8 +238,7 @@ class ServerConnectTask extends Task {
|
||||||
* @throws LoginException login failure
|
* @throws LoginException login failure
|
||||||
*/
|
*/
|
||||||
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
||||||
TaskMonitor monitor)
|
TaskMonitor monitor) throws IOException, LoginException, CancelledException {
|
||||||
throws IOException, LoginException, CancelledException {
|
|
||||||
|
|
||||||
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
||||||
|
|
||||||
|
@ -296,7 +296,8 @@ class ServerConnectTask extends Task {
|
||||||
"Client PKI certificate has not been installed");
|
"Client PKI certificate has not been installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate()) {
|
if (ApplicationKeyManagerFactory
|
||||||
|
.usingGeneratedSelfSignedCertificate()) {
|
||||||
Msg.warn(this,
|
Msg.warn(this,
|
||||||
"Server connect - client is using self-signed PKI certificate");
|
"Server connect - client is using self-signed PKI certificate");
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,20 @@ public interface GhidraServerHandle extends Remote {
|
||||||
* - version 9.1 switched to using SSL/TLS for RMI registry connection preventing
|
* - version 9.1 switched to using SSL/TLS for RMI registry connection preventing
|
||||||
* older clients the ability to connect to the server. Remote interface remained
|
* older clients the ability to connect to the server. Remote interface remained
|
||||||
* unchanged allowing 9.1 clients to connect to 9.0 server.
|
* unchanged allowing 9.1 clients to connect to 9.0 server.
|
||||||
|
* 12: Revised RepositoryFile serialization to facilitate support for text-data used
|
||||||
|
* for link-file storage.
|
||||||
*/
|
*/
|
||||||
public static final int INTERFACE_VERSION = 11;
|
|
||||||
|
/**
|
||||||
|
* The server interface version that the server will use and is the maximum version that the
|
||||||
|
* client can operate with.
|
||||||
|
*/
|
||||||
|
public static final int INTERFACE_VERSION = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum server interface version that the client can operate with.
|
||||||
|
*/
|
||||||
|
public static final int MINIMUM_INTERFACE_VERSION = 11;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum version of Ghidra which utilized the current INTERFACE_VERSION
|
* Minimum version of Ghidra which utilized the current INTERFACE_VERSION
|
||||||
|
|
|
@ -70,6 +70,10 @@ public interface RemoteRepositoryHandle extends RepositoryHandle, Remote {
|
||||||
int bufferSize, String contentType, String projectPath)
|
int bufferSize, String contentType, String projectPath)
|
||||||
throws IOException, InvalidNameException;
|
throws IOException, InvalidNameException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void createTextDataFile(String parentPath, String itemName, String fileID, String contentType,
|
||||||
|
String textData, String comment) throws InvalidNameException, IOException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ManagedBufferFileHandle openDatabase(String parentPath, String itemName, int version,
|
ManagedBufferFileHandle openDatabase(String parentPath, String itemName, int version,
|
||||||
int minChangeDataVer) throws IOException;
|
int minChangeDataVer) throws IOException;
|
||||||
|
|
|
@ -123,6 +123,21 @@ public interface RepositoryHandle {
|
||||||
*/
|
*/
|
||||||
RepositoryItem getItem(String fileID) throws IOException;
|
RepositoryItem getItem(String fileID) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new text data file within the specified parent folder.
|
||||||
|
* @param parentPath folder path of parent
|
||||||
|
* @param itemName new data file name
|
||||||
|
* @param fileID unique file ID
|
||||||
|
* @param contentType application defined content type
|
||||||
|
* @param textData text data (required)
|
||||||
|
* @param comment file comment (may be null)
|
||||||
|
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||||
|
* @throws InvalidNameException if the name has illegal characters.
|
||||||
|
* @throws IOException if an IO error occurs.
|
||||||
|
*/
|
||||||
|
void createTextDataFile(String parentPath, String itemName, String fileID, String contentType,
|
||||||
|
String textData, String comment) throws InvalidNameException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new empty database item within the repository.
|
* Create a new empty database item within the repository.
|
||||||
* @param parentPath parent folder path
|
* @param parentPath parent folder path
|
||||||
|
@ -138,8 +153,8 @@ public interface RepositoryHandle {
|
||||||
* @throws InvalidNameException if itemName or parentPath contains invalid characters
|
* @throws InvalidNameException if itemName or parentPath contains invalid characters
|
||||||
*/
|
*/
|
||||||
ManagedBufferFileHandle createDatabase(String parentPath, String itemName, String fileID,
|
ManagedBufferFileHandle createDatabase(String parentPath, String itemName, String fileID,
|
||||||
int bufferSize, String contentType, String projectPath) throws IOException,
|
int bufferSize, String contentType, String projectPath)
|
||||||
InvalidNameException;
|
throws IOException, InvalidNameException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open an existing version of a database buffer file for non-update read-only use.
|
* Open an existing version of a database buffer file for non-update read-only use.
|
||||||
|
@ -212,8 +227,8 @@ public interface RepositoryHandle {
|
||||||
* @throws DuplicateFileException if target item already exists
|
* @throws DuplicateFileException if target item already exists
|
||||||
* @throws IOException if an IO error occurs
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
void moveItem(String oldParentPath, String newParentPath, String oldItemName, String newItemName)
|
void moveItem(String oldParentPath, String newParentPath, String oldItemName,
|
||||||
throws InvalidNameException, IOException;
|
String newItemName) throws InvalidNameException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a checkout on the specified item.
|
* Perform a checkout on the specified item.
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.remote;
|
package ghidra.framework.remote;
|
||||||
|
|
||||||
import ghidra.framework.store.FileSystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InvalidClassException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import ghidra.framework.store.FileSystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>RepositoryItemStatus</code> provides status information for a
|
* <code>RepositoryItemStatus</code> provides status information for a
|
||||||
|
@ -25,18 +28,36 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class RepositoryItem implements java.io.Serializable {
|
public class RepositoryItem implements java.io.Serializable {
|
||||||
|
|
||||||
|
// Serial version 2 supports an expandable schema which allows a newer repository server
|
||||||
|
// to remain usable by older clients, and a newer client to deserialize data from an older
|
||||||
|
// server. The optional schema version if present can be used to identify the additional
|
||||||
|
// serialized data which may following the schema version number.
|
||||||
|
|
||||||
public final static long serialVersionUID = 2L;
|
public final static long serialVersionUID = 2L;
|
||||||
|
|
||||||
public final static int FILE = 1;
|
private static final byte SERIALIZATION_SCHEMA_VERSION = 1;
|
||||||
public final static int DATABASE = 2;
|
|
||||||
|
|
||||||
protected String folderPath;
|
public final static int FILE = 1; // DataFileItem (not yet supported)
|
||||||
protected String itemName;
|
public final static int DATABASE = 2; // DatabaseItem
|
||||||
protected String fileID;
|
public final static int TEXT_DATA_FILE = 3; // TextDataItem
|
||||||
protected int itemType;
|
|
||||||
protected String contentType;
|
//
|
||||||
protected int version;
|
// Client use can support reading from older server which presents serialVersionUID==2
|
||||||
protected long versionTime;
|
//
|
||||||
|
|
||||||
|
private String folderPath;
|
||||||
|
private String itemName;
|
||||||
|
private String fileID;
|
||||||
|
private int itemType;
|
||||||
|
private String contentType;
|
||||||
|
private int version;
|
||||||
|
private long versionTime;
|
||||||
|
|
||||||
|
// Variables below were added after serialVersionUID == 2 was established and rely on
|
||||||
|
// additional serialization version byte to identify the optional data fields added
|
||||||
|
// after original serialVersionUID == 2 fields.
|
||||||
|
|
||||||
|
private String textData; // applies to TEXT_DATA_FILE introduced with GhidraServerHandle v12
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor needed for de-serialization
|
* Default constructor needed for de-serialization
|
||||||
|
@ -53,9 +74,10 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
* @param contentType content type associated with item
|
* @param contentType content type associated with item
|
||||||
* @param version repository item version or -1 if versioning not supported
|
* @param version repository item version or -1 if versioning not supported
|
||||||
* @param versionTime version creation time
|
* @param versionTime version creation time
|
||||||
|
* @param textData related text data (may be null)
|
||||||
*/
|
*/
|
||||||
public RepositoryItem(String folderPath, String itemName, String fileID, int itemType,
|
public RepositoryItem(String folderPath, String itemName, String fileID, int itemType,
|
||||||
String contentType, int version, long versionTime) {
|
String contentType, int version, long versionTime, String textData) {
|
||||||
this.folderPath = folderPath;
|
this.folderPath = folderPath;
|
||||||
this.itemName = itemName;
|
this.itemName = itemName;
|
||||||
this.fileID = fileID;
|
this.fileID = fileID;
|
||||||
|
@ -63,6 +85,7 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.versionTime = versionTime;
|
this.versionTime = versionTime;
|
||||||
|
this.textData = textData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +94,7 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
* @throws IOException if an IO error occurs
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
|
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
|
||||||
|
|
||||||
out.writeLong(serialVersionUID);
|
out.writeLong(serialVersionUID);
|
||||||
out.writeUTF(folderPath);
|
out.writeUTF(folderPath);
|
||||||
out.writeUTF(itemName);
|
out.writeUTF(itemName);
|
||||||
|
@ -79,6 +103,12 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
out.writeUTF(contentType != null ? contentType : "");
|
out.writeUTF(contentType != null ? contentType : "");
|
||||||
out.writeInt(version);
|
out.writeInt(version);
|
||||||
out.writeLong(versionTime);
|
out.writeLong(versionTime);
|
||||||
|
|
||||||
|
// Variables below were added after serialVersionUID == 2 was established
|
||||||
|
|
||||||
|
out.writeByte(SERIALIZATION_SCHEMA_VERSION);
|
||||||
|
out.writeUTF(textData != null ? textData : "");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,11 +117,11 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
* @throws ClassNotFoundException if unrecognized serialVersionUID detected
|
* @throws ClassNotFoundException if unrecognized serialVersionUID detected
|
||||||
*/
|
*/
|
||||||
private void readObject(java.io.ObjectInputStream in) throws IOException,
|
private void readObject(java.io.ObjectInputStream in)
|
||||||
ClassNotFoundException {
|
throws IOException, ClassNotFoundException {
|
||||||
long serialVersion = in.readLong();
|
long serialVersion = in.readLong();
|
||||||
if (serialVersion != serialVersionUID) {
|
if (serialVersion != serialVersionUID) {
|
||||||
throw new ClassNotFoundException("Unsupported version of RepositoryItemStatus");
|
throw new ClassNotFoundException("Unsupported version of RepositoryItem");
|
||||||
}
|
}
|
||||||
folderPath = in.readUTF();
|
folderPath = in.readUTF();
|
||||||
itemName = in.readUTF();
|
itemName = in.readUTF();
|
||||||
|
@ -106,6 +136,31 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
}
|
}
|
||||||
version = in.readInt();
|
version = in.readInt();
|
||||||
versionTime = in.readLong();
|
versionTime = in.readLong();
|
||||||
|
|
||||||
|
// Variable handling below was added after serialVersionUID == 2 was established
|
||||||
|
|
||||||
|
int available = in.available();
|
||||||
|
if (available == 0) {
|
||||||
|
// assume original schema before serializationSchemaVersion was employed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we do not serialize class implementations with RMI the older client must be able to
|
||||||
|
// read the initial data sequence that was previously supported. Newer clients that have this
|
||||||
|
// class will use the presence of the version byte to handle communicating with either an
|
||||||
|
// older server (no version byte) or a newer server (version byte and subsequent data is read)
|
||||||
|
byte serializationSchemaVersion = in.readByte();
|
||||||
|
if (serializationSchemaVersion < 1 ||
|
||||||
|
serializationSchemaVersion > SERIALIZATION_SCHEMA_VERSION) {
|
||||||
|
throw new InvalidClassException("RepositoryItem",
|
||||||
|
"RepositoryItem has incompatible serialization schema version: " +
|
||||||
|
serializationSchemaVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
textData = in.readUTF();
|
||||||
|
if (StringUtils.isBlank(textData)) {
|
||||||
|
textData = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,4 +217,11 @@ public class RepositoryItem implements java.io.Serializable {
|
||||||
return versionTime;
|
return versionTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get related text data
|
||||||
|
* @return text data or null
|
||||||
|
*/
|
||||||
|
public String getTextData() {
|
||||||
|
return textData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,7 +23,7 @@ import java.io.OutputStream;
|
||||||
* <code>DataFileItem</code> corresponds to a private serialized
|
* <code>DataFileItem</code> corresponds to a private serialized
|
||||||
* data file within a FileSystem. Methods are provided for opening
|
* data file within a FileSystem. Methods are provided for opening
|
||||||
* the underlying file as an input or output stream.
|
* the underlying file as an input or output stream.
|
||||||
* <br>
|
* <P>
|
||||||
* NOTE: The use of DataFile is not encouraged and is not fully
|
* NOTE: The use of DataFile is not encouraged and is not fully
|
||||||
* supported.
|
* supported.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,10 +16,13 @@
|
||||||
package ghidra.framework.store;
|
package ghidra.framework.store;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
import db.buffers.BufferFile;
|
import db.buffers.BufferFile;
|
||||||
import db.buffers.ManagedBufferFile;
|
import db.buffers.ManagedBufferFile;
|
||||||
import ghidra.framework.store.local.UnknownFolderItem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
|
import ghidra.framework.store.remote.RemoteFileSystem;
|
||||||
import ghidra.util.InvalidNameException;
|
import ghidra.util.InvalidNameException;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -40,38 +43,38 @@ public interface FileSystem {
|
||||||
* Get user name associated with this filesystem. In the case of a remote filesystem
|
* Get user name associated with this filesystem. In the case of a remote filesystem
|
||||||
* this will correspond to the name used during login/authentication. A null value may
|
* this will correspond to the name used during login/authentication. A null value may
|
||||||
* be returned if user name unknown.
|
* be returned if user name unknown.
|
||||||
|
* @return user name used to authenticate or null if not-applicable
|
||||||
*/
|
*/
|
||||||
String getUserName();
|
public String getUserName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the file-system requires check-outs when
|
* {@return true if the file-system requires check-outs when
|
||||||
* modifying folder items.
|
* modifying folder items.}
|
||||||
*/
|
*/
|
||||||
public boolean isVersioned();
|
public boolean isVersioned();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if file-system is on-line.
|
* {@return true if file-system is on-line.}
|
||||||
*/
|
*/
|
||||||
public boolean isOnline();
|
public boolean isOnline();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if file-system is read-only.
|
* {@return true if file-system is read-only.}
|
||||||
* @throws IOException
|
* @throws IOException if IO error occurs
|
||||||
*/
|
*/
|
||||||
public boolean isReadOnly() throws IOException;
|
public boolean isReadOnly() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of folder items contained within this file-system.
|
* {@return the number of folder items contained within this file-system.}
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
* @throws UnsupportedOperationException if file-system does not support this operation
|
* @throws UnsupportedOperationException if file-system does not support this operation
|
||||||
*/
|
*/
|
||||||
public int getItemCount() throws IOException, UnsupportedOperationException;
|
public int getItemCount() throws IOException, UnsupportedOperationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of the folder item names contained in the given folder.
|
* {@return a list of the folder item names contained in the given folder.}
|
||||||
* @param folderPath the path of the folder.
|
* @param folderPath the path of the folder.
|
||||||
* @return a list of folder item names.
|
* @throws IOException if an IO error occurs
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
public String[] getItemNames(String folderPath) throws IOException;
|
public String[] getItemNames(String folderPath) throws IOException;
|
||||||
|
|
||||||
|
@ -81,7 +84,7 @@ public interface FileSystem {
|
||||||
* @return a list of folder items. Null items may exist if index contained item name
|
* @return a list of folder items. Null items may exist if index contained item name
|
||||||
* while storage was not found. An {@link UnknownFolderItem} may be returned if unsupported
|
* while storage was not found. An {@link UnknownFolderItem} may be returned if unsupported
|
||||||
* item storage encountered.
|
* item storage encountered.
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
public FolderItem[] getItems(String folderPath) throws IOException;
|
public FolderItem[] getItems(String folderPath) throws IOException;
|
||||||
|
|
||||||
|
@ -105,6 +108,8 @@ public interface FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of subfolders (by name) that are stored within the specified folder path.
|
* Return a list of subfolders (by name) that are stored within the specified folder path.
|
||||||
|
* @param folderPath folder path
|
||||||
|
* @return subfolders names
|
||||||
* @throws FileNotFoundException if folder path does not exist.
|
* @throws FileNotFoundException if folder path does not exist.
|
||||||
* @throws IOException if IO error occurs.
|
* @throws IOException if IO error occurs.
|
||||||
*/
|
*/
|
||||||
|
@ -122,6 +127,16 @@ public interface FileSystem {
|
||||||
public void createFolder(String parentPath, String folderName)
|
public void createFolder(String parentPath, String folderName)
|
||||||
throws InvalidNameException, IOException;
|
throws InvalidNameException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the specified folder item is supported by this filesystem's interface and
|
||||||
|
* storage. This method primarily exists to determine if a remote server can support
|
||||||
|
* the specified content. This can come into play as new storage formats are added
|
||||||
|
* to a {@link LocalFileSystem} but may not be supported by a connected {@link RemoteFileSystem}.
|
||||||
|
* @param folderItem folder item
|
||||||
|
* @return true if folder item storage is supported
|
||||||
|
*/
|
||||||
|
public boolean isSupportedItemType(FolderItem folderItem);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new database item within the specified parent folder using the contents
|
* Create a new database item within the specified parent folder using the contents
|
||||||
* of the specified BufferFile.
|
* of the specified BufferFile.
|
||||||
|
@ -162,8 +177,7 @@ public interface FileSystem {
|
||||||
* @return an empty BufferFile open for read-write.
|
* @return an empty BufferFile open for read-write.
|
||||||
* @throws FileNotFoundException thrown if parent folder does not exist.
|
* @throws FileNotFoundException thrown if parent folder does not exist.
|
||||||
* @throws DuplicateFileException if a folder item exists with this name
|
* @throws DuplicateFileException if a folder item exists with this name
|
||||||
* @throws InvalidNameException if the name does not have
|
* @throws InvalidNameException if the name has illegal characters.
|
||||||
* all alphanumerics
|
|
||||||
* @throws IOException if an IO error occurs.
|
* @throws IOException if an IO error occurs.
|
||||||
*/
|
*/
|
||||||
public ManagedBufferFile createDatabase(String parentPath, String name, String fileID,
|
public ManagedBufferFile createDatabase(String parentPath, String name, String fileID,
|
||||||
|
@ -182,7 +196,6 @@ public interface FileSystem {
|
||||||
* @return new data file
|
* @return new data file
|
||||||
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||||
* @throws InvalidNameException if the name has illegal characters.
|
* @throws InvalidNameException if the name has illegal characters.
|
||||||
* all alphanumerics
|
|
||||||
* @throws IOException if an IO error occurs.
|
* @throws IOException if an IO error occurs.
|
||||||
* @throws CancelledException if cancelled by monitor
|
* @throws CancelledException if cancelled by monitor
|
||||||
*/
|
*/
|
||||||
|
@ -190,6 +203,23 @@ public interface FileSystem {
|
||||||
String comment, String contentType, TaskMonitor monitor)
|
String comment, String contentType, TaskMonitor monitor)
|
||||||
throws InvalidNameException, IOException, CancelledException;
|
throws InvalidNameException, IOException, CancelledException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new text data file within the specified parent folder.
|
||||||
|
* @param parentPath folder path of parent
|
||||||
|
* @param name new data file name
|
||||||
|
* @param fileID file ID to be associated with new file or null
|
||||||
|
* @param contentType application defined content type
|
||||||
|
* @param textData text data (required)
|
||||||
|
* @param comment file comment (may be null, only used if versioning is enabled)
|
||||||
|
* @return new data file
|
||||||
|
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||||
|
* @throws InvalidNameException if the name has illegal characters.
|
||||||
|
* @throws IOException if an IO error occurs.
|
||||||
|
*/
|
||||||
|
public TextDataItem createTextDataItem(String parentPath, String name, String fileID,
|
||||||
|
String contentType, String textData, String comment)
|
||||||
|
throws InvalidNameException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new file item from a packed file.
|
* Creates a new file item from a packed file.
|
||||||
* The content/item type must be determined from the input stream.
|
* The content/item type must be determined from the input stream.
|
||||||
|
@ -252,7 +282,8 @@ public interface FileSystem {
|
||||||
* Moves the specified item to a new folder.
|
* Moves the specified item to a new folder.
|
||||||
* @param folderPath path of folder containing the item.
|
* @param folderPath path of folder containing the item.
|
||||||
* @param name name of the item to be moved.
|
* @param name name of the item to be moved.
|
||||||
* @param newFolderPath path of folder where item is to be moved.
|
* @param newFolderPath path of folder where item is to be moved to.
|
||||||
|
* @param newName new item name to be applied
|
||||||
* @throws FileNotFoundException if the item does not exist.
|
* @throws FileNotFoundException if the item does not exist.
|
||||||
* @throws DuplicateFileException if item with the same name exists within the new parent folder.
|
* @throws DuplicateFileException if item with the same name exists within the new parent folder.
|
||||||
* @throws FileInUseException if the item is in-use or checked-out
|
* @throws FileInUseException if the item is in-use or checked-out
|
||||||
|
@ -263,14 +294,14 @@ public interface FileSystem {
|
||||||
throws IOException, InvalidNameException;
|
throws IOException, InvalidNameException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given listener to be notified of file system changes.
|
* Adds a file system listener to be notified of file system changes.
|
||||||
* @param listener the listener to be added.
|
* @param listener the listener to be added.
|
||||||
*/
|
*/
|
||||||
public void addFileSystemListener(FileSystemListener listener);
|
public void addFileSystemListener(FileSystemListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the listener from being notified of file system changes.
|
* Removes a file system listener from being notified of file system changes.
|
||||||
* @param listener
|
* @param listener file system listener
|
||||||
*/
|
*/
|
||||||
public void removeFileSystemListener(FileSystemListener listener);
|
public void removeFileSystemListener(FileSystemListener listener);
|
||||||
|
|
||||||
|
@ -283,7 +314,7 @@ public interface FileSystem {
|
||||||
public boolean folderExists(String folderPath) throws IOException;
|
public boolean folderExists(String folderPath) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the file exists
|
* {@return true if the file exists}
|
||||||
* @param folderPath the folderPath of the folder that may contain the file.
|
* @param folderPath the folderPath of the folder that may contain the file.
|
||||||
* @param name the name of the file to check for existence.
|
* @param name the name of the file to check for existence.
|
||||||
* @throws IOException if an IO error occurs.
|
* @throws IOException if an IO error occurs.
|
||||||
|
@ -291,7 +322,7 @@ public interface FileSystem {
|
||||||
public boolean fileExists(String folderPath, String name) throws IOException;
|
public boolean fileExists(String folderPath, String name) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this file system is shared
|
* {@return true if this file system is shared}
|
||||||
*/
|
*/
|
||||||
public boolean isShared();
|
public boolean isShared();
|
||||||
|
|
||||||
|
@ -300,4 +331,58 @@ public interface FileSystem {
|
||||||
*/
|
*/
|
||||||
public void dispose();
|
public void dispose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize an absolute path, removing all "." and ".." use.
|
||||||
|
* <P>
|
||||||
|
* NOTE: This method does not consider possible linked folder traversal which may
|
||||||
|
* get ignored when flattening/simplifying path.
|
||||||
|
*
|
||||||
|
* @param path absolute filesystem path which may contain "." or ".." path elements.
|
||||||
|
* @return normalized path
|
||||||
|
* @throws IllegalArgumentException if an absolute path starting with {@link #SEPARATOR}
|
||||||
|
* was not specified or an illegal path was specified.
|
||||||
|
*/
|
||||||
|
public static String normalizePath(String path) throws IllegalArgumentException {
|
||||||
|
if (!path.startsWith(SEPARATOR)) {
|
||||||
|
throw new IllegalArgumentException("Absolute path required");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] split = path.split(SEPARATOR);
|
||||||
|
|
||||||
|
ArrayList<String> elements = new ArrayList<>();
|
||||||
|
for (int i = 1; i < split.length; i++) {
|
||||||
|
String e = split[i];
|
||||||
|
if (e.length() == 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid path with empty element: " + path);
|
||||||
|
}
|
||||||
|
if ("..".equals(e)) {
|
||||||
|
try {
|
||||||
|
// remove last element
|
||||||
|
elements.removeLast();
|
||||||
|
}
|
||||||
|
catch (NoSuchElementException ex) {
|
||||||
|
throw new IllegalArgumentException("Invalid path: " + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (".".equals(e)) {
|
||||||
|
// ignore element
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elements.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.isEmpty()) {
|
||||||
|
return SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
for (String e : elements) {
|
||||||
|
buf.append(SEPARATOR);
|
||||||
|
buf.append(e);
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,11 @@ public interface FolderItem {
|
||||||
*/
|
*/
|
||||||
public static final int DATAFILE_FILE_TYPE = 1;
|
public static final int DATAFILE_FILE_TYPE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item type is associated with metadata only (e.g., URL)
|
||||||
|
*/
|
||||||
|
public static final int LINK_FILE_TYPE = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default checkout ID used when a checkout is not applicable.
|
* Default checkout ID used when a checkout is not applicable.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>TextDataItem</code> corresponds to a file which contains text data only
|
||||||
|
* and relies only on property file storage (i.e., no separate database or data file).
|
||||||
|
*/
|
||||||
|
public interface TextDataItem extends FolderItem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text data that was stored with this item
|
||||||
|
* @return text data
|
||||||
|
*/
|
||||||
|
public String getTextData();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>UnknownFolderItem</code> corresponds to a folder item which has an unknown storage type
|
||||||
|
* or has encountered a storage failure.
|
||||||
|
*/
|
||||||
|
public interface UnknownFolderItem extends FolderItem {
|
||||||
|
|
||||||
|
public static final String UNKNOWN_CONTENT_TYPE = "Unknown-File";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file type:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link FolderItem#DATABASE_FILE_TYPE}</li>
|
||||||
|
* <li>{@link FolderItem#DATAFILE_FILE_TYPE}</li>
|
||||||
|
* <li>{@link FolderItem#LINK_FILE_TYPE}</li>
|
||||||
|
* </ul>
|
||||||
|
* @return file type or {@link FolderItem#UNKNOWN_FILE_TYPE} (-1) if unknown
|
||||||
|
*/
|
||||||
|
public int getFileType();
|
||||||
|
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ import utilities.util.FileUtilities;
|
||||||
/**
|
/**
|
||||||
* <code>PackedDatabase</code> provides a packed form of Database
|
* <code>PackedDatabase</code> provides a packed form of Database
|
||||||
* which compresses a single version into a file.
|
* which compresses a single version into a file.
|
||||||
* <br>
|
* <P>
|
||||||
* When opening a packed database, a PackedDBHandle is returned
|
* When opening a packed database, a PackedDBHandle is returned
|
||||||
* after first expanding the file into a temporary Database.
|
* after first expanding the file into a temporary Database.
|
||||||
*/
|
*/
|
||||||
|
@ -276,8 +276,8 @@ public class PackedDatabase extends Database {
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
* @throws CancelledException if unpack/open is cancelled
|
* @throws CancelledException if unpack/open is cancelled
|
||||||
*/
|
*/
|
||||||
public static synchronized PackedDatabase getPackedDatabase(ResourceFile packedDbFile, boolean neverCache,
|
public static synchronized PackedDatabase getPackedDatabase(ResourceFile packedDbFile,
|
||||||
TaskMonitor monitor) throws IOException, CancelledException {
|
boolean neverCache, TaskMonitor monitor) throws IOException, CancelledException {
|
||||||
if (!neverCache && PackedDatabaseCache.isEnabled()) {
|
if (!neverCache && PackedDatabaseCache.isEnabled()) {
|
||||||
try {
|
try {
|
||||||
return PackedDatabaseCache.getCache().getCachedDB(packedDbFile, monitor);
|
return PackedDatabaseCache.getCache().getCachedDB(packedDbFile, monitor);
|
||||||
|
@ -633,7 +633,7 @@ public class PackedDatabase extends Database {
|
||||||
tmpFile = Application.createTempFile("pack", ".tmp");
|
tmpFile = Application.createTempFile("pack", ".tmp");
|
||||||
tmpFile.delete();
|
tmpFile.delete();
|
||||||
dbh.saveAs(tmpFile, false, monitor);
|
dbh.saveAs(tmpFile, false, monitor);
|
||||||
try (InputStream itemIn = new BufferedInputStream(new FileInputStream(tmpFile))){
|
try (InputStream itemIn = new BufferedInputStream(new FileInputStream(tmpFile))) {
|
||||||
ItemSerializer.outputItem(itemName, contentType, FolderItem.DATABASE_FILE_TYPE,
|
ItemSerializer.outputItem(itemName, contentType, FolderItem.DATABASE_FILE_TYPE,
|
||||||
tmpFile.length(), itemIn, outputFile, monitor);
|
tmpFile.length(), itemIn, outputFile, monitor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -598,7 +598,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean addFileToIndex(PropertyFile pfile) throws IOException, NotFoundException {
|
private boolean addFileToIndex(ItemPropertyFile pfile) throws IOException, NotFoundException {
|
||||||
|
|
||||||
String parentPath = pfile.getParentPath();
|
String parentPath = pfile.getParentPath();
|
||||||
String name = pfile.getName();
|
String name = pfile.getName();
|
||||||
|
@ -832,7 +832,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
||||||
catch (NotFoundException e) {
|
catch (NotFoundException e) {
|
||||||
// ignore - handled below
|
// ignore - handled below
|
||||||
}
|
}
|
||||||
throw new FileNotFoundException("Item not found: " + folderPath + SEPARATOR + itemName);
|
throw new FileNotFoundException("Item not found: " + getPath(folderPath, itemName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1207,7 +1207,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
||||||
String newFolderPath = folder.getPathname();
|
String newFolderPath = folder.getPathname();
|
||||||
for (Item item : folder.items.values()) {
|
for (Item item : folder.items.values()) {
|
||||||
ItemStorage itemStorage = item.itemStorage;
|
ItemStorage itemStorage = item.itemStorage;
|
||||||
PropertyFile pfile = item.itemStorage.getPropertyFile();
|
ItemPropertyFile pfile = item.itemStorage.getPropertyFile();
|
||||||
pfile.moveTo(itemStorage.dir, itemStorage.storageName, newFolderPath,
|
pfile.moveTo(itemStorage.dir, itemStorage.storageName, newFolderPath,
|
||||||
itemStorage.itemName);
|
itemStorage.itemName);
|
||||||
itemStorage.folderPath = newFolderPath;
|
itemStorage.folderPath = newFolderPath;
|
||||||
|
@ -1236,7 +1236,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
||||||
folder = getFolder(folderPath, GetFolderOption.READ_ONLY);
|
folder = getFolder(folderPath, GetFolderOption.READ_ONLY);
|
||||||
if (folder.parent.folders.get(newFolderName) != null) {
|
if (folder.parent.folders.get(newFolderName) != null) {
|
||||||
throw new DuplicateFileException(
|
throw new DuplicateFileException(
|
||||||
parentPath + SEPARATOR + newFolderName + " already exists.");
|
getPath(parentPath, newFolderName) + " already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
indexJournal.moveFolder(folderPath, getPath(parentPath, newFolderName));
|
indexJournal.moveFolder(folderPath, getPath(parentPath, newFolderName));
|
||||||
|
@ -1462,7 +1462,6 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void replayJournal() throws IndexReadException {
|
private void replayJournal() throws IndexReadException {
|
||||||
Msg.info(this, "restoring data storage index...");
|
|
||||||
int lineNum = 0;
|
int lineNum = 0;
|
||||||
BufferedReader journalReader = null;
|
BufferedReader journalReader = null;
|
||||||
try {
|
try {
|
||||||
|
@ -1778,7 +1777,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
PropertyFile getPropertyFile() throws IOException {
|
ItemPropertyFile getPropertyFile() throws IOException {
|
||||||
return new IndexedPropertyFile(dir, storageName, folderPath, itemName);
|
return new IndexedPropertyFile(dir, storageName, folderPath, itemName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,59 +15,68 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.store.local;
|
package ghidra.framework.store.local;
|
||||||
|
|
||||||
import ghidra.framework.store.FileSystem;
|
|
||||||
import ghidra.util.PropertyFile;
|
|
||||||
import ghidra.util.exception.DuplicateFileException;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
public class IndexedPropertyFile extends PropertyFile {
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
|
|
||||||
public final static String NAME_PROPERTY = "NAME";
|
public class IndexedPropertyFile extends ItemPropertyFile {
|
||||||
public final static String PARENT_PATH_PROPERTY = "PARENT";
|
|
||||||
|
protected static final String NAME_PROPERTY = "NAME";
|
||||||
|
protected static final String PARENT_PATH_PROPERTY = "PARENT";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new or existing PropertyFile.
|
* Construct a new or existing PropertyFile.
|
||||||
* This form ignores retained property values for NAME and PARENT path.
|
* This constructor ignores retained property values for NAME and PARENT path.
|
||||||
|
* This constructor will not throw an exception if the file does not exist.
|
||||||
* @param dir parent directory
|
* @param dir parent directory
|
||||||
* @param storageName stored property file name (without extension)
|
* @param storageName stored property file name (without extension)
|
||||||
* @param parentPath path to parent
|
* @param parentPath path to parent
|
||||||
* @param name name of the property file
|
* @param name name of the property file
|
||||||
* @throws IOException
|
* @throws InvalidObjectException if a file parse error occurs
|
||||||
|
* @throws IOException if an IO error occurs reading an existing file
|
||||||
*/
|
*/
|
||||||
public IndexedPropertyFile(File dir, String storageName, String parentPath, String name)
|
public IndexedPropertyFile(File dir, String storageName, String parentPath, String name)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
super(dir, storageName, parentPath, name);
|
super(dir, storageName, parentPath, name);
|
||||||
// if (exists() &&
|
if (contains(NAME_PROPERTY) && contains(PARENT_PATH_PROPERTY)) {
|
||||||
// (!name.equals(getString(NAME_PROPERTY, null)) || !parentPath.equals(getString(
|
this.name = getString(NAME_PROPERTY, name);
|
||||||
// PARENT_PATH_PROPERTY, null)))) {
|
this.parentPath = getString(PARENT_PATH_PROPERTY, parentPath);
|
||||||
// throw new AssertException();
|
}
|
||||||
// }
|
else {
|
||||||
|
// new property file
|
||||||
putString(NAME_PROPERTY, name);
|
putString(NAME_PROPERTY, name);
|
||||||
putString(PARENT_PATH_PROPERTY, parentPath);
|
putString(PARENT_PATH_PROPERTY, parentPath);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an existing PropertyFile.
|
* Construct a existing PropertyFile.
|
||||||
|
* This constructor uses property values for NAME and PARENT path.
|
||||||
* @param dir parent directory
|
* @param dir parent directory
|
||||||
* @param storageName stored property file name (without extension)
|
* @param storageName stored property file name (without extension)
|
||||||
* @throws FileNotFoundException if property file does not exist
|
* @throws FileNotFoundException if property file does not exist
|
||||||
|
* @throws InvalidObjectException if a file parse error occurs
|
||||||
* @throws IOException if error occurs reading property file
|
* @throws IOException if error occurs reading property file
|
||||||
*/
|
*/
|
||||||
public IndexedPropertyFile(File dir, String storageName) throws IOException {
|
public IndexedPropertyFile(File dir, String storageName) throws IOException {
|
||||||
super(dir, storageName, FileSystem.SEPARATOR, storageName);
|
super(dir, storageName, null, null);
|
||||||
if (!exists()) {
|
if (!exists()) {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException(
|
||||||
|
new File(dir, storageName + PROPERTY_EXT) + " not found");
|
||||||
}
|
}
|
||||||
|
name = getString(NAME_PROPERTY, null);
|
||||||
|
parentPath = getString(PARENT_PATH_PROPERTY, null);
|
||||||
if (name == null || parentPath == null) {
|
if (name == null || parentPath == null) {
|
||||||
throw new IOException("Invalid indexed property file: " + propertyFile);
|
throw new IOException("Invalid indexed property file: " + propertyFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an existing PropertyFile.
|
* Construct a existing PropertyFile.
|
||||||
* @param file
|
* This constructor uses property values for NAME and PARENT path.
|
||||||
|
* @param file property file
|
||||||
* @throws FileNotFoundException if property file does not exist
|
* @throws FileNotFoundException if property file does not exist
|
||||||
|
* @throws InvalidObjectException if a file parse error occurs
|
||||||
* @throws IOException if error occurs reading property file
|
* @throws IOException if error occurs reading property file
|
||||||
*/
|
*/
|
||||||
public IndexedPropertyFile(File file) throws IOException {
|
public IndexedPropertyFile(File file) throws IOException {
|
||||||
|
@ -82,27 +90,17 @@ public class IndexedPropertyFile extends PropertyFile {
|
||||||
return propertyFileName.substring(0, propertyFileName.length() - PROPERTY_EXT.length());
|
return propertyFileName.substring(0, propertyFileName.length() - PROPERTY_EXT.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readState() throws IOException {
|
|
||||||
super.readState();
|
|
||||||
name = getString(NAME_PROPERTY, null);
|
|
||||||
parentPath = getString(PARENT_PATH_PROPERTY, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveTo(File newParent, String newStorageName, String newParentPath, String newName)
|
public void moveTo(File newParent, String newStorageName, String newParentPath, String newName)
|
||||||
throws DuplicateFileException, IOException {
|
throws DuplicateFileException, IOException {
|
||||||
|
String oldName = name;
|
||||||
|
String oldParentPath = parentPath;
|
||||||
super.moveTo(newParent, newStorageName, newParentPath, newName);
|
super.moveTo(newParent, newStorageName, newParentPath, newName);
|
||||||
// if (!parentPath.equals(newParentPath)) {
|
if (!newParentPath.equals(oldParentPath) || !newName.equals(oldName)) {
|
||||||
// throw new AssertException();
|
putString(NAME_PROPERTY, name);
|
||||||
// }
|
putString(PARENT_PATH_PROPERTY, parentPath);
|
||||||
// if (!name.equals(newName)) {
|
|
||||||
// throw new AssertException();
|
|
||||||
// }
|
|
||||||
putString(NAME_PROPERTY, newName);
|
|
||||||
putString(PARENT_PATH_PROPERTY, newParentPath);
|
|
||||||
writeState();
|
writeState();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import java.io.*;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.PropertyFile;
|
|
||||||
import ghidra.util.exception.NotFoundException;
|
import ghidra.util.exception.NotFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,7 +93,7 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void fileIdChanged(PropertyFile pfile, String oldFileId)
|
protected synchronized void fileIdChanged(ItemPropertyFile pfile, String oldFileId)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
indexJournal.open();
|
indexJournal.open();
|
||||||
try {
|
try {
|
||||||
|
@ -143,12 +142,19 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
ItemStorage itemStorage = item.itemStorage;
|
||||||
try {
|
try {
|
||||||
PropertyFile propertyFile = item.itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
if (propertyFile.exists()) {
|
if (propertyFile.exists()) {
|
||||||
return LocalFolderItem.getFolderItem(this, propertyFile);
|
return LocalFolderItem.getFolderItem(this, propertyFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (InvalidObjectException e) {
|
||||||
|
// Use unknown placeholder item on failure
|
||||||
|
InvalidPropertyFile invalidFile = new InvalidPropertyFile(itemStorage.dir,
|
||||||
|
itemStorage.storageName, itemStorage.folderPath, itemStorage.itemName);
|
||||||
|
return new LocalUnknownFolderItem(this, invalidFile);
|
||||||
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.store.local;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link InvalidPropertyFile} provides a substitue {@link ItemPropertyFile} when one
|
||||||
|
* fails to parse. This allows the item's existance to be managed even if the item cannot
|
||||||
|
* be opened.
|
||||||
|
*/
|
||||||
|
public class InvalidPropertyFile extends ItemPropertyFile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an invalid property file instance if it previously failed to parse.
|
||||||
|
* @param dir native directory where this file is stored
|
||||||
|
* @param storageName stored property file name (without extension)
|
||||||
|
* @param parentPath logical parent path for the associated item
|
||||||
|
* @param name name of the associated item
|
||||||
|
* @throws IOException (never thrown since file is never read)
|
||||||
|
*/
|
||||||
|
public InvalidPropertyFile(File dir, String storageName, String parentPath, String name)
|
||||||
|
throws IOException {
|
||||||
|
super(dir, storageName, parentPath, name);
|
||||||
|
// NOTE: IOException is prevented by having a do-nothing readState method below
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void readState() {
|
||||||
|
// avoid potential parse failure
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.store.local;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import javax.help.UnsupportedOperationException;
|
||||||
|
|
||||||
|
import ghidra.framework.store.FileSystem;
|
||||||
|
import ghidra.framework.store.FolderItem;
|
||||||
|
import ghidra.util.PropertyFile;
|
||||||
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ItemPropertyFile} provides basic property storage which is primarily intended to
|
||||||
|
* store limited information related to a logical {@link FolderItem}. The file
|
||||||
|
* extension used is {@link #PROPERTY_EXT}.
|
||||||
|
*/
|
||||||
|
public class ItemPropertyFile extends PropertyFile {
|
||||||
|
|
||||||
|
private static final String FILE_ID_PROPERTY = "FILE_ID";
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
protected String parentPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new or existing PropertyFile.
|
||||||
|
* This constructor ignores retained property values for NAME and PARENT path.
|
||||||
|
* This constructor will not throw an exception if the file does not exist.
|
||||||
|
* @param dir native directory where this file is stored
|
||||||
|
* @param storageName stored property file name (without extension)
|
||||||
|
* @param parentPath logical parent path for the associated item
|
||||||
|
* @param name name of the associated item
|
||||||
|
* @throws InvalidObjectException if a file parse error occurs
|
||||||
|
* @throws IOException if an IO error occurs reading an existing file
|
||||||
|
*/
|
||||||
|
public ItemPropertyFile(File dir, String storageName, String parentPath, String name)
|
||||||
|
throws IOException {
|
||||||
|
super(dir, storageName);
|
||||||
|
this.name = name;
|
||||||
|
this.parentPath = parentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the item associated with this PropertyFile. A null value may be returned
|
||||||
|
* if this is an older property file and the name was not specified at
|
||||||
|
* time of construction.
|
||||||
|
* @return associated item name or null if unknown
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the logical path of the item associated with this PropertyFile. A null value may be
|
||||||
|
* returned if this is an older property file and the name and parentPath was not specified at
|
||||||
|
* time of construction.
|
||||||
|
* @return logical path of the associated item or null if unknown
|
||||||
|
*/
|
||||||
|
public String getPath() {
|
||||||
|
if (parentPath == null || name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (parentPath.length() == 1) {
|
||||||
|
return parentPath + name;
|
||||||
|
}
|
||||||
|
return parentPath + FileSystem.SEPARATOR_CHAR + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the logical parent path containing the item descibed by this PropertyFile.
|
||||||
|
* @return logical parent directory path
|
||||||
|
*/
|
||||||
|
public String getParentPath() {
|
||||||
|
return parentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the FileID associated with this file.
|
||||||
|
* @return FileID associated with this file or null
|
||||||
|
*/
|
||||||
|
public String getFileID() {
|
||||||
|
return getString(FILE_ID_PROPERTY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the FileID associated with this file.
|
||||||
|
* @param fileId unique file ID
|
||||||
|
*/
|
||||||
|
public void setFileID(String fileId) {
|
||||||
|
putString(FILE_ID_PROPERTY, fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move this PropertyFile to the newParent file.
|
||||||
|
* @param newStorageParent new storage parent of the native file
|
||||||
|
* @param newStorageName new storage name for this property file
|
||||||
|
* @param newParentPath new logical parent path
|
||||||
|
* @param newName new logical item name
|
||||||
|
* @throws IOException thrown if there was a problem accessing the
|
||||||
|
* @throws DuplicateFileException thrown if a file with the newName
|
||||||
|
* already exists
|
||||||
|
*/
|
||||||
|
public void moveTo(File newStorageParent, String newStorageName, String newParentPath,
|
||||||
|
String newName) throws DuplicateFileException, IOException {
|
||||||
|
super.moveTo(newStorageParent, newStorageName);
|
||||||
|
if (!newParentPath.equals(parentPath) || !newName.equals(name)) {
|
||||||
|
parentPath = newParentPath;
|
||||||
|
name = newName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE!! This method must not be used.
|
||||||
|
* <P>
|
||||||
|
* Movement of an item is related to its logical pathname and must be accomplished
|
||||||
|
* with the {@link #moveTo(File, String, String, String)} method. There is no supported
|
||||||
|
* direct use of this method.
|
||||||
|
*
|
||||||
|
* @param newStorageParent new storage parent of the native file
|
||||||
|
* @param newStorageName new storage name for this property file
|
||||||
|
* @throws UnsupportedOperationException always thrown
|
||||||
|
* @deprecated method must not be used
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = false, since = "11.4")
|
||||||
|
@Override
|
||||||
|
public final void moveTo(File newStorageParent, String newStorageName)
|
||||||
|
throws UnsupportedOperationException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,27 +17,38 @@ package ghidra.framework.store.local;
|
||||||
|
|
||||||
import ghidra.framework.store.DataFileItem;
|
import ghidra.framework.store.DataFileItem;
|
||||||
import ghidra.framework.store.FolderItem;
|
import ghidra.framework.store.FolderItem;
|
||||||
import ghidra.util.PropertyFile;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.DuplicateFileException;
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>LocalDataFile</code> provides a FolderItem implementation
|
* <code>LocalDataFileItem</code> provides a FolderItem implementation
|
||||||
* for a local serialized data file. This implementation supports
|
* for a local serialized data file. This implementation supports
|
||||||
* a non-versioned file-system only.
|
* a non-versioned file-system only.
|
||||||
* <p>
|
* <p>
|
||||||
* This item utilizes a data directory for storing the serialized
|
* This item utilizes a data directory for storing the serialized
|
||||||
* data file.
|
* data file.
|
||||||
|
* <p>
|
||||||
|
* NOTE: The use of this file item type is not fully supported.
|
||||||
*/
|
*/
|
||||||
public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
public class LocalDataFileItem extends LocalFolderItem implements DataFileItem {
|
||||||
|
|
||||||
private final static int IO_BUFFER_SIZE = 32 * 1024;
|
private final static int IO_BUFFER_SIZE = 32 * 1024;
|
||||||
private static final String DATA_FILE = "data.1.gdf";
|
private static final String DATA_FILE = "data.1.gdf";
|
||||||
|
|
||||||
public LocalDataFile(LocalFileSystem fileSystem, PropertyFile propertyFile) throws IOException {
|
/**
|
||||||
|
* Constructor for an existing local serialized=data file item which corresponds to the specified
|
||||||
|
* property file.
|
||||||
|
* @param fileSystem file system
|
||||||
|
* @param propertyFile database property file
|
||||||
|
* @throws IOException if an IO Error occurs
|
||||||
|
*/
|
||||||
|
public LocalDataFileItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
|
||||||
|
throws IOException {
|
||||||
super(fileSystem, propertyFile, true, false);
|
super(fileSystem, propertyFile, true, false);
|
||||||
|
|
||||||
if (fileSystem.isVersioned()) {
|
if (fileSystem.isVersioned()) {
|
||||||
|
@ -50,7 +61,7 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new local data file item.
|
* Create a new local serialized-data file item.
|
||||||
* @param fileSystem file system
|
* @param fileSystem file system
|
||||||
* @param propertyFile serialized data property file
|
* @param propertyFile serialized data property file
|
||||||
* @param istream data source input stream (should be a start of data and will be read to end of file).
|
* @param istream data source input stream (should be a start of data and will be read to end of file).
|
||||||
|
@ -61,9 +72,9 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
||||||
* @throws IOException if an IO Error occurs
|
* @throws IOException if an IO Error occurs
|
||||||
* @throws CancelledException if monitor cancels operation
|
* @throws CancelledException if monitor cancels operation
|
||||||
*/
|
*/
|
||||||
public LocalDataFile(LocalFileSystem fileSystem, PropertyFile propertyFile,
|
public LocalDataFileItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
|
||||||
InputStream istream, String contentType, TaskMonitor monitor) throws IOException,
|
InputStream istream, String contentType, TaskMonitor monitor)
|
||||||
CancelledException {
|
throws IOException, CancelledException {
|
||||||
super(fileSystem, propertyFile, true, true);
|
super(fileSystem, propertyFile, true, true);
|
||||||
|
|
||||||
if (fileSystem.isVersioned()) {
|
if (fileSystem.isVersioned()) {
|
||||||
|
@ -71,6 +82,11 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
||||||
throw new UnsupportedOperationException("Versioning not yet supported for DataFiles");
|
throw new UnsupportedOperationException("Versioning not yet supported for DataFiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(contentType)) {
|
||||||
|
abortCreate();
|
||||||
|
throw new IllegalArgumentException("Missing content-type");
|
||||||
|
}
|
||||||
|
|
||||||
File dataFile = getDataFile();
|
File dataFile = getDataFile();
|
||||||
if (dataFile.exists()) {
|
if (dataFile.exists()) {
|
||||||
throw new DuplicateFileException(getName() + " already exists.");
|
throw new DuplicateFileException(getName() + " already exists.");
|
|
@ -18,10 +18,13 @@ package ghidra.framework.store.local;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import db.buffers.*;
|
import db.buffers.*;
|
||||||
import ghidra.framework.store.*;
|
import ghidra.framework.store.*;
|
||||||
import ghidra.framework.store.db.*;
|
import ghidra.framework.store.db.*;
|
||||||
import ghidra.util.*;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.ReadOnlyException;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -49,8 +52,8 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
||||||
* @param create if true the data directory will be created
|
* @param create if true the data directory will be created
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, boolean create)
|
private LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
|
||||||
throws IOException {
|
boolean create) throws IOException {
|
||||||
super(fileSystem, propertyFile, true, create);
|
super(fileSystem, propertyFile, true, create);
|
||||||
if (isVersioned) {
|
if (isVersioned) {
|
||||||
versionedDbListener = new LocalVersionedDbListener();
|
versionedDbListener = new LocalVersionedDbListener();
|
||||||
|
@ -63,7 +66,8 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
||||||
* @param fileSystem file system
|
* @param fileSystem file system
|
||||||
* @param propertyFile database property file
|
* @param propertyFile database property file
|
||||||
*/
|
*/
|
||||||
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile) throws IOException {
|
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
|
||||||
|
throws IOException {
|
||||||
super(fileSystem, propertyFile, true, false);
|
super(fileSystem, propertyFile, true, false);
|
||||||
|
|
||||||
if (isVersioned) {
|
if (isVersioned) {
|
||||||
|
@ -94,11 +98,16 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
||||||
* @throws IOException if error occurs
|
* @throws IOException if error occurs
|
||||||
* @throws CancelledException if database creation cancelled by user
|
* @throws CancelledException if database creation cancelled by user
|
||||||
*/
|
*/
|
||||||
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, BufferFile srcFile,
|
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, BufferFile srcFile,
|
||||||
String contentType, String fileID, String comment, boolean resetDatabaseId,
|
String contentType, String fileID, String comment, boolean resetDatabaseId,
|
||||||
TaskMonitor monitor, String user) throws IOException, CancelledException {
|
TaskMonitor monitor, String user) throws IOException, CancelledException {
|
||||||
super(fileSystem, propertyFile, true, true);
|
super(fileSystem, propertyFile, true, true);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(contentType)) {
|
||||||
|
abortCreate();
|
||||||
|
throw new IllegalArgumentException("Missing content-type");
|
||||||
|
}
|
||||||
|
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
long checkoutId = DEFAULT_CHECKOUT_ID;
|
long checkoutId = DEFAULT_CHECKOUT_ID;
|
||||||
try {
|
try {
|
||||||
|
@ -154,7 +163,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
||||||
* @throws IOException if error occurs
|
* @throws IOException if error occurs
|
||||||
* @throws CancelledException if database creation cancelled by user
|
* @throws CancelledException if database creation cancelled by user
|
||||||
*/
|
*/
|
||||||
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, File packedFile,
|
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, File packedFile,
|
||||||
String contentType, TaskMonitor monitor, String user)
|
String contentType, TaskMonitor monitor, String user)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
super(fileSystem, propertyFile, true, true);
|
super(fileSystem, propertyFile, true, true);
|
||||||
|
@ -222,7 +231,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
||||||
* @throws IOException if error occurs
|
* @throws IOException if error occurs
|
||||||
*/
|
*/
|
||||||
static LocalManagedBufferFile create(final LocalFileSystem fileSystem,
|
static LocalManagedBufferFile create(final LocalFileSystem fileSystem,
|
||||||
PropertyFile propertyFile, int bufferSize, String contentType, String fileID,
|
ItemPropertyFile propertyFile, int bufferSize, String contentType, String fileID,
|
||||||
String user, String projectPath) throws IOException {
|
String user, String projectPath) throws IOException {
|
||||||
|
|
||||||
final LocalDatabaseItem dbItem = new LocalDatabaseItem(fileSystem, propertyFile, true);
|
final LocalDatabaseItem dbItem = new LocalDatabaseItem(fileSystem, propertyFile, true);
|
||||||
|
@ -257,6 +266,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
||||||
db.setSynchronizationObject(dbItem.fileSystem);
|
db.setSynchronizationObject(dbItem.fileSystem);
|
||||||
dbItem.privateDb = (PrivateDatabase) db;
|
dbItem.privateDb = (PrivateDatabase) db;
|
||||||
}
|
}
|
||||||
|
dbItem.log("file created", user);
|
||||||
dbItem.fireItemCreated();
|
dbItem.fireItemCreated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a local filesystem for existing data
|
* Construct a local filesystem for existing data
|
||||||
* @param rootPath
|
* @param rootPath filesystem root directory (the directory must exist and must not have any
|
||||||
* @param create
|
* contents if {@code create} is true)
|
||||||
* @param isVersioned
|
* @param create true if creating new filesystem from the empty directory at rootPath
|
||||||
* @param readOnly
|
* @param isVersioned true if creating a versioned filesystem
|
||||||
* @param enableAsyncronousDispatching
|
* @param readOnly true if file system is read-only (ignored if {@code create} is true).
|
||||||
|
* @param enableAsyncronousDispatching true if async event dispatching should be performed
|
||||||
* @return local filesystem
|
* @return local filesystem
|
||||||
* @throws FileNotFoundException if specified rootPath does not exist
|
* @throws FileNotFoundException if specified rootPath does not exist
|
||||||
* @throws IOException if error occurs while reading/writing index files
|
* @throws IOException if error occurs while reading/writing index files
|
||||||
|
@ -103,10 +104,6 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
throw new IOException("new filesystem directory is not empty: " + rootPath);
|
throw new IOException("new filesystem directory is not empty: " + rootPath);
|
||||||
}
|
}
|
||||||
if (create) {
|
if (create) {
|
||||||
// if (isCreateMangledFileSystemEnabled()) {
|
|
||||||
// return new MangledLocalFileSystem(rootPath, isVersioned, readOnly,
|
|
||||||
// enableAsyncronousDispatching);
|
|
||||||
// }
|
|
||||||
return new IndexedV1LocalFileSystem(rootPath, isVersioned, readOnly,
|
return new IndexedV1LocalFileSystem(rootPath, isVersioned, readOnly,
|
||||||
enableAsyncronousDispatching, true);
|
enableAsyncronousDispatching, true);
|
||||||
}
|
}
|
||||||
|
@ -154,7 +151,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
/**
|
/**
|
||||||
* Returns true if any file found within dir whose name starts
|
* Returns true if any file found within dir whose name starts
|
||||||
* with '~' character (e.g., ~index.dat, etc)
|
* with '~' character (e.g., ~index.dat, etc)
|
||||||
* @param dir
|
* @param dir directory to inspect
|
||||||
* @return true if any hidden file found with '~' prefix
|
* @return true if any hidden file found with '~' prefix
|
||||||
*/
|
*/
|
||||||
private static boolean hasAnyHiddenFiles(File dir) {
|
private static boolean hasAnyHiddenFiles(File dir) {
|
||||||
|
@ -237,7 +234,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associate file system with a specific repository logger
|
* Associate file system with a specific repository logger
|
||||||
* @param repositoryLogger
|
* @param repositoryLogger repository logger (may be null)
|
||||||
*/
|
*/
|
||||||
public void setAssociatedRepositoryLogger(RepositoryLogger repositoryLogger) {
|
public void setAssociatedRepositoryLogger(RepositoryLogger repositoryLogger) {
|
||||||
this.repositoryLogger = repositoryLogger;
|
this.repositoryLogger = repositoryLogger;
|
||||||
|
@ -317,8 +314,14 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
return pfile.exists();
|
return pfile.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyFile getPropertyFile() throws IOException {
|
/**
|
||||||
return new PropertyFile(dir, storageName, folderPath, itemName);
|
* Get property file associated with this item storage
|
||||||
|
* @return property file
|
||||||
|
* @throws InvalidObjectException if a file parse error occurs
|
||||||
|
* @throws IOException if an IO error occurs reading an existing file
|
||||||
|
*/
|
||||||
|
ItemPropertyFile getPropertyFile() throws IOException {
|
||||||
|
return new ItemPropertyFile(dir, storageName, folderPath, itemName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -336,19 +339,19 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an existing storage location
|
* Find an existing storage location
|
||||||
* @param folderPath
|
* @param folderPath folder path of item
|
||||||
* @param itemName
|
* @param itemName item name
|
||||||
* @return storage location. A non-null value does not guarantee that the associated
|
* @return storage location. A non-null value does not guarantee that the associated
|
||||||
* item actually exists.
|
* item actually exists.
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException if existing storage allocation not found
|
||||||
*/
|
*/
|
||||||
protected abstract ItemStorage findItemStorage(String folderPath, String itemName)
|
protected abstract ItemStorage findItemStorage(String folderPath, String itemName)
|
||||||
throws FileNotFoundException;
|
throws FileNotFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate a new storage location
|
* Allocate a new storage location
|
||||||
* @param folderPath
|
* @param folderPath folder path of item
|
||||||
* @param itemName
|
* @param itemName item name
|
||||||
* @return storage location
|
* @return storage location
|
||||||
* @throws DuplicateFileException if item path has previously been allocated
|
* @throws DuplicateFileException if item path has previously been allocated
|
||||||
* @throws IOException if invalid path/item name specified
|
* @throws IOException if invalid path/item name specified
|
||||||
|
@ -359,9 +362,9 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deallocate item storage
|
* Deallocate item storage
|
||||||
* @param folderPath
|
* @param folderPath folder path of item
|
||||||
* @param itemName
|
* @param itemName item name
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
protected abstract void deallocateItemStorage(String folderPath, String itemName)
|
protected abstract void deallocateItemStorage(String folderPath, String itemName)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
@ -376,15 +379,27 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized LocalFolderItem getItem(String folderPath, String name) throws IOException {
|
public synchronized LocalFolderItem getItem(String folderPath, String name) throws IOException {
|
||||||
|
ItemStorage itemStorage = null;
|
||||||
try {
|
try {
|
||||||
ItemStorage itemStorage = findItemStorage(folderPath, name);
|
itemStorage = findItemStorage(folderPath, name);
|
||||||
if (itemStorage == null) {
|
if (itemStorage == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
if (propertyFile.exists()) {
|
if (propertyFile.exists()) {
|
||||||
return LocalFolderItem.getFolderItem(this, propertyFile);
|
return LocalFolderItem.getFolderItem(this, propertyFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// force cleanup of bad storage allocation
|
||||||
|
Msg.warn(this, "Attempting item cleanup due to missing property file: " +
|
||||||
|
new File(propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
|
||||||
|
itemDeleted(folderPath, name);
|
||||||
|
}
|
||||||
|
catch (InvalidObjectException e) {
|
||||||
|
// Use unknown placeholder item on failure
|
||||||
|
InvalidPropertyFile invalidFile = new InvalidPropertyFile(itemStorage.dir,
|
||||||
|
itemStorage.storageName, itemStorage.folderPath, itemStorage.itemName);
|
||||||
|
return new LocalUnknownFolderItem(this, invalidFile);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
@ -394,11 +409,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification that FileID has been changed within propertyFile
|
* Notification that FileID has been changed within propertyFile
|
||||||
* @param propertyFile
|
* @param propertyFile item property file
|
||||||
* @param oldFileId
|
* @param oldFileId old FileId
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
protected void fileIdChanged(PropertyFile propertyFile, String oldFileId) throws IOException {
|
protected void fileIdChanged(ItemPropertyFile propertyFile, String oldFileId)
|
||||||
|
throws IOException {
|
||||||
// do nothing by default
|
// do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,6 +434,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
return folderItems;
|
return folderItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupportedItemType(FolderItem folderItem) {
|
||||||
|
return (folderItem instanceof DatabaseItem) || (folderItem instanceof TextDataItem) ||
|
||||||
|
(folderItem instanceof DataFileItem);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized LocalDatabaseItem createDatabase(String parentPath, String name,
|
public synchronized LocalDatabaseItem createDatabase(String parentPath, String name,
|
||||||
String fileID, BufferFile bufferFile, String comment, String contentType,
|
String fileID, BufferFile bufferFile, String comment, String contentType,
|
||||||
|
@ -434,7 +456,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||||
LocalDatabaseItem item = null;
|
LocalDatabaseItem item = null;
|
||||||
try {
|
try {
|
||||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID,
|
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID,
|
||||||
comment, resetDatabaseId, monitor, user);
|
comment, resetDatabaseId, monitor, user);
|
||||||
}
|
}
|
||||||
|
@ -462,7 +484,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
ItemStorage itemStorage = allocateItemStorage(parentPath, hiddenName);
|
ItemStorage itemStorage = allocateItemStorage(parentPath, hiddenName);
|
||||||
LocalDatabaseItem item = null;
|
LocalDatabaseItem item = null;
|
||||||
try {
|
try {
|
||||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID, null,
|
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID, null,
|
||||||
resetDatabaseId, monitor, null);
|
resetDatabaseId, monitor, null);
|
||||||
}
|
}
|
||||||
|
@ -489,7 +511,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||||
LocalManagedBufferFile bufferFile = null;
|
LocalManagedBufferFile bufferFile = null;
|
||||||
try {
|
try {
|
||||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
bufferFile = LocalDatabaseItem.create(this, propertyFile, bufferSize, contentType,
|
bufferFile = LocalDatabaseItem.create(this, propertyFile, bufferSize, contentType,
|
||||||
fileID, user, projectPath);
|
fileID, user, projectPath);
|
||||||
}
|
}
|
||||||
|
@ -502,7 +524,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized LocalDataFile createDataFile(String parentPath, String name,
|
public synchronized LocalDataFileItem createDataFile(String parentPath, String name,
|
||||||
InputStream istream, String comment, String contentType, TaskMonitor monitor)
|
InputStream istream, String comment, String contentType, TaskMonitor monitor)
|
||||||
throws InvalidNameException, IOException, CancelledException {
|
throws InvalidNameException, IOException, CancelledException {
|
||||||
|
|
||||||
|
@ -514,11 +536,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
testValidName(name, false);
|
testValidName(name, false);
|
||||||
|
|
||||||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||||
LocalDataFile dataFile = null;
|
LocalDataFileItem dataFile = null;
|
||||||
try {
|
try {
|
||||||
//TODO handle comment
|
//TODO handle comment
|
||||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
dataFile = new LocalDataFile(this, propertyFile, istream, contentType, monitor);
|
dataFile = new LocalDataFileItem(this, propertyFile, istream, contentType, monitor);
|
||||||
|
dataFile.log("file created", getUserName());
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (dataFile == null) {
|
if (dataFile == null) {
|
||||||
|
@ -531,6 +554,38 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
return dataFile;
|
return dataFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized LocalTextDataItem createTextDataItem(String parentPath, String name,
|
||||||
|
String fileID, String contentType, String textData, String ignoredComment)
|
||||||
|
throws InvalidNameException, IOException {
|
||||||
|
|
||||||
|
// comment is ignored
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
throw new ReadOnlyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
testValidName(parentPath, true);
|
||||||
|
testValidName(name, false);
|
||||||
|
|
||||||
|
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||||
|
LocalTextDataItem linkFile = null;
|
||||||
|
try {
|
||||||
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
|
linkFile = new LocalTextDataItem(this, propertyFile, fileID, contentType, textData);
|
||||||
|
linkFile.log("file created", getUserName());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (linkFile == null) {
|
||||||
|
deallocateItemStorage(parentPath, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventManager.itemCreated(parentPath, name);
|
||||||
|
|
||||||
|
return linkFile;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalDatabaseItem createFile(String parentPath, String name, File packedFile,
|
public LocalDatabaseItem createFile(String parentPath, String name, File packedFile,
|
||||||
TaskMonitor monitor, String user)
|
TaskMonitor monitor, String user)
|
||||||
|
@ -561,7 +616,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||||
LocalDatabaseItem item = null;
|
LocalDatabaseItem item = null;
|
||||||
try {
|
try {
|
||||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||||
item =
|
item =
|
||||||
new LocalDatabaseItem(this, propertyFile, packedFile, contentType, monitor, user);
|
new LocalDatabaseItem(this, propertyFile, packedFile, contentType, monitor, user);
|
||||||
}
|
}
|
||||||
|
@ -661,6 +716,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns file system listener.
|
* Returns file system listener.
|
||||||
|
* @return file system listener or null
|
||||||
*/
|
*/
|
||||||
FileSystemListener getListener() {
|
FileSystemListener getListener() {
|
||||||
return eventManager;
|
return eventManager;
|
||||||
|
@ -716,6 +772,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param c character to check
|
||||||
* @return true if c is a valid character within the FileSystem.
|
* @return true if c is a valid character within the FileSystem.
|
||||||
*/
|
*/
|
||||||
public static boolean isValidNameCharacter(char c) {
|
public static boolean isValidNameCharacter(char c) {
|
||||||
|
@ -756,9 +813,9 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
/**
|
/**
|
||||||
* Notify the filesystem that the property file and associated data files for
|
* Notify the filesystem that the property file and associated data files for
|
||||||
* an item have been removed from the filesystem.
|
* an item have been removed from the filesystem.
|
||||||
* @param folderPath
|
* @param folderPath folder path of item
|
||||||
* @param itemName
|
* @param itemName item name
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
protected synchronized void itemDeleted(String folderPath, String itemName) throws IOException {
|
protected synchronized void itemDeleted(String folderPath, String itemName) throws IOException {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -768,6 +825,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
* Returns the full path for a specific folder or item
|
* Returns the full path for a specific folder or item
|
||||||
* @param parentPath full parent path
|
* @param parentPath full parent path
|
||||||
* @param name child folder or item name
|
* @param name child folder or item name
|
||||||
|
* @return pathname
|
||||||
*/
|
*/
|
||||||
protected final static String getPath(String parentPath, String name) {
|
protected final static String getPath(String parentPath, String name) {
|
||||||
if (parentPath.length() == 1) {
|
if (parentPath.length() == 1) {
|
||||||
|
@ -848,7 +906,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape hidden prefix chars in name
|
* Escape hidden prefix chars in name
|
||||||
* @param name
|
* @param name name to be escaped
|
||||||
* @return escaped name
|
* @return escaped name
|
||||||
*/
|
*/
|
||||||
public static final String escapeHiddenDirPrefixChars(String name) {
|
public static final String escapeHiddenDirPrefixChars(String name) {
|
||||||
|
@ -867,7 +925,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unescape a non-hidden directory name
|
* Unescape a non-hidden directory name
|
||||||
* @param name
|
* @param name name to be unescaped
|
||||||
* @return unescaped name or null if name is a hidden name
|
* @return unescaped name or null if name is a hidden name
|
||||||
*/
|
*/
|
||||||
public static final String unescapeHiddenDirPrefixChars(String name) {
|
public static final String unescapeHiddenDirPrefixChars(String name) {
|
||||||
|
|
|
@ -21,7 +21,8 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import ghidra.framework.store.*;
|
import ghidra.framework.store.*;
|
||||||
import ghidra.util.*;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.ReadOnlyException;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
@ -49,7 +50,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
|
|
||||||
static final String DATA_DIR_EXTENSION = ".db";
|
static final String DATA_DIR_EXTENSION = ".db";
|
||||||
|
|
||||||
final PropertyFile propertyFile;
|
final ItemPropertyFile propertyFile;
|
||||||
final CheckoutManager checkoutMgr;
|
final CheckoutManager checkoutMgr;
|
||||||
final HistoryManager historyMgr;
|
final HistoryManager historyMgr;
|
||||||
final LocalFileSystem fileSystem;
|
final LocalFileSystem fileSystem;
|
||||||
|
@ -69,7 +70,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
* @param fileSystem file system
|
* @param fileSystem file system
|
||||||
* @param propertyFile property file
|
* @param propertyFile property file
|
||||||
*/
|
*/
|
||||||
LocalFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
|
LocalFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile) {
|
||||||
this.fileSystem = fileSystem;
|
this.fileSystem = fileSystem;
|
||||||
this.propertyFile = propertyFile;
|
this.propertyFile = propertyFile;
|
||||||
this.isVersioned = fileSystem.isVersioned();
|
this.isVersioned = fileSystem.isVersioned();
|
||||||
|
@ -90,7 +91,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
* @param create if true the data directory will be created
|
* @param create if true the data directory will be created
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
LocalFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile, boolean useDataDir,
|
LocalFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, boolean useDataDir,
|
||||||
boolean create) throws IOException {
|
boolean create) throws IOException {
|
||||||
this.fileSystem = fileSystem;
|
this.fileSystem = fileSystem;
|
||||||
this.propertyFile = propertyFile;
|
this.propertyFile = propertyFile;
|
||||||
|
@ -121,7 +122,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
throw new FileNotFoundException(getName() + " not found");
|
throw new FileNotFoundException(getName() + " not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVersioned) {
|
if (isVersioned && useDataDir) {
|
||||||
checkoutMgr = new CheckoutManager(this, create);
|
checkoutMgr = new CheckoutManager(this, create);
|
||||||
historyMgr = new HistoryManager(this, create);
|
historyMgr = new HistoryManager(this, create);
|
||||||
}
|
}
|
||||||
|
@ -161,7 +162,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
final File getDataDir() {
|
final File getDataDir() {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
// Use hidden DB directory
|
// Use hidden DB directory
|
||||||
return new File(propertyFile.getFolder(),
|
return new File(propertyFile.getParentStorageDirectory(),
|
||||||
LocalFileSystem.HIDDEN_DIR_PREFIX +
|
LocalFileSystem.HIDDEN_DIR_PREFIX +
|
||||||
LocalFileSystem.escapeHiddenDirPrefixChars(propertyFile.getStorageName()) +
|
LocalFileSystem.escapeHiddenDirPrefixChars(propertyFile.getStorageName()) +
|
||||||
DATA_DIR_EXTENSION);
|
DATA_DIR_EXTENSION);
|
||||||
|
@ -234,6 +235,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
*/
|
*/
|
||||||
void beginCheckin(long checkoutId) throws FileInUseException {
|
void beginCheckin(long checkoutId) throws FileInUseException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
if (checkoutMgr == null) {
|
||||||
|
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||||
|
}
|
||||||
if (checkinId != DEFAULT_CHECKOUT_ID) {
|
if (checkinId != DEFAULT_CHECKOUT_ID) {
|
||||||
ItemCheckoutStatus status;
|
ItemCheckoutStatus status;
|
||||||
try {
|
try {
|
||||||
|
@ -426,7 +430,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
checkInUse();
|
checkInUse();
|
||||||
|
|
||||||
File oldFolder = propertyFile.getFolder();
|
File oldFolder = propertyFile.getParentStorageDirectory();
|
||||||
String oldStorageName = propertyFile.getStorageName();
|
String oldStorageName = propertyFile.getStorageName();
|
||||||
String oldPath = propertyFile.getParentPath();
|
String oldPath = propertyFile.getParentPath();
|
||||||
File oldDbDir = getDataDir();
|
File oldDbDir = getDataDir();
|
||||||
|
@ -491,41 +495,6 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
return propertyFile.getName();
|
return propertyFile.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Change the name of this item's property file and hidden data directory
|
|
||||||
// * based upon the new item name.
|
|
||||||
// * If in-use files prevent renaming a FileInUseException will be thrown.
|
|
||||||
// * @param name new name for this item
|
|
||||||
// * @throws InvalidNameException invalid name was specified
|
|
||||||
// * @throws IOException an error occurred
|
|
||||||
// */
|
|
||||||
// void doSetName(String name) throws InvalidNameException, IOException {
|
|
||||||
// synchronized (fileSystem) {
|
|
||||||
// File oldDbDir = getDataDir();
|
|
||||||
// String oldName = getName();
|
|
||||||
//
|
|
||||||
// boolean success = false;
|
|
||||||
// try {
|
|
||||||
// propertyFile.setName(name);
|
|
||||||
// File newDbDir = getDataDir();
|
|
||||||
// if (useDataDir) {
|
|
||||||
// if (newDbDir.exists()) {
|
|
||||||
// throw new DuplicateFileException(getName() + " already exists");
|
|
||||||
// }
|
|
||||||
// else if (!oldDbDir.renameTo(newDbDir)) {
|
|
||||||
// throw new FileInUseException(oldName + " is in use");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// success = true;
|
|
||||||
// }
|
|
||||||
// finally {
|
|
||||||
// if (!success && !propertyFile.getName().equals(oldName)) {
|
|
||||||
// propertyFile.setName(oldName);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see ghidra.framework.store.FolderItem#getParentPath()
|
* @see ghidra.framework.store.FolderItem#getParentPath()
|
||||||
*/
|
*/
|
||||||
|
@ -590,6 +559,10 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Non-versioned item does not support getVersions");
|
"Non-versioned item does not support getVersions");
|
||||||
}
|
}
|
||||||
|
if (historyMgr == null) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"getVersions not supported without history manager");
|
||||||
|
}
|
||||||
return historyMgr.getVersions();
|
return historyMgr.getVersions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -652,12 +625,16 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
@Override
|
@Override
|
||||||
public ItemCheckoutStatus checkout(CheckoutType checkoutType, String user, String projectPath)
|
public ItemCheckoutStatus checkout(CheckoutType checkoutType, String user, String projectPath)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
if (checkoutMgr == null) {
|
||||||
|
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||||
|
}
|
||||||
if (!isVersioned) {
|
if (!isVersioned) {
|
||||||
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
|
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
|
||||||
}
|
}
|
||||||
if (fileSystem.isReadOnly()) {
|
if (fileSystem.isReadOnly()) {
|
||||||
throw new ReadOnlyException();
|
throw new ReadOnlyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
|
||||||
ItemCheckoutStatus coStatus =
|
ItemCheckoutStatus coStatus =
|
||||||
|
@ -672,6 +649,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void terminateCheckout(long checkoutId, boolean notify) throws IOException {
|
public void terminateCheckout(long checkoutId, boolean notify) throws IOException {
|
||||||
|
if (checkoutMgr == null) {
|
||||||
|
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||||
|
}
|
||||||
if (!isVersioned) {
|
if (!isVersioned) {
|
||||||
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
|
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
|
||||||
}
|
}
|
||||||
|
@ -700,6 +680,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Non-versioned item does not support checkout");
|
"Non-versioned item does not support checkout");
|
||||||
}
|
}
|
||||||
|
if (checkoutMgr == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return checkoutMgr.getCheckout(checkoutId);
|
return checkoutMgr.getCheckout(checkoutId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -711,6 +694,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Non-versioned item does not support checkout");
|
"Non-versioned item does not support checkout");
|
||||||
}
|
}
|
||||||
|
if (checkoutMgr == null) {
|
||||||
|
return new ItemCheckoutStatus[0];
|
||||||
|
}
|
||||||
return checkoutMgr.getAllCheckouts();
|
return checkoutMgr.getAllCheckouts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -802,33 +788,39 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
* @param propertyFile property file which identifies the folder item.
|
* @param propertyFile property file which identifies the folder item.
|
||||||
* @return folder item
|
* @return folder item
|
||||||
*/
|
*/
|
||||||
static LocalFolderItem getFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
|
static LocalFolderItem getFolderItem(LocalFileSystem fileSystem,
|
||||||
|
ItemPropertyFile propertyFile) {
|
||||||
int fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
|
int fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
|
||||||
try {
|
try {
|
||||||
if (fileType == DATAFILE_FILE_TYPE) {
|
if (fileType == DATAFILE_FILE_TYPE) {
|
||||||
return new LocalDataFile(fileSystem, propertyFile);
|
return new LocalDataFileItem(fileSystem, propertyFile);
|
||||||
}
|
}
|
||||||
else if (fileType == DATABASE_FILE_TYPE) {
|
else if (fileType == DATABASE_FILE_TYPE) {
|
||||||
return new LocalDatabaseItem(fileSystem, propertyFile);
|
return new LocalDatabaseItem(fileSystem, propertyFile);
|
||||||
}
|
}
|
||||||
|
else if (fileType == LINK_FILE_TYPE) {
|
||||||
|
return new LocalTextDataItem(fileSystem, propertyFile);
|
||||||
|
}
|
||||||
else if (fileType == UNKNOWN_FILE_TYPE) {
|
else if (fileType == UNKNOWN_FILE_TYPE) {
|
||||||
log.error("Folder item has unspecified file type: " +
|
log.error("Folder item has unspecified file type: " + new File(
|
||||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()));
|
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.error("Folder item has unsupported file type (" + fileType + "): " +
|
log.error("Folder item has unsupported file type (" + fileType + "): " + new File(
|
||||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()));
|
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException e) {
|
||||||
log.error("Folder item may be corrupt due to missing file: " +
|
log.error("Folder item may be corrupt due to missing file: " +
|
||||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()), e);
|
new File(propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()),
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
log.error("Folder item may be corrupt: " +
|
log.error("Folder item may be corrupt: " +
|
||||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()), e);
|
new File(propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()),
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
return new UnknownFolderItem(fileSystem, propertyFile);
|
return new LocalUnknownFolderItem(fileSystem, propertyFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -836,7 +828,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (isVersioned) {
|
if (isVersioned) {
|
||||||
try {
|
try {
|
||||||
return checkoutMgr.isCheckedOut();
|
return checkoutMgr != null && checkoutMgr.isCheckedOut();
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.error(getName() + " versioning error", e);
|
Msg.error(getName() + " versioning error", e);
|
||||||
|
@ -865,6 +857,11 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return propertyFile.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update this non-versioned item with the latest version of the specified versioned item.
|
* Update this non-versioned item with the latest version of the specified versioned item.
|
||||||
* @param versionedFolderItem versioned item which corresponds to this
|
* @param versionedFolderItem versioned item which corresponds to this
|
||||||
|
@ -892,6 +889,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
@Override
|
@Override
|
||||||
public void updateCheckoutVersion(long checkoutId, int checkoutVersion, String user)
|
public void updateCheckoutVersion(long checkoutId, int checkoutVersion, String user)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
if (checkoutMgr == null) {
|
||||||
|
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||||
|
}
|
||||||
if (!isVersioned) {
|
if (!isVersioned) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"updateCheckoutVersion is not applicable to non-versioned item");
|
"updateCheckoutVersion is not applicable to non-versioned item");
|
||||||
|
@ -907,4 +907,5 @@ public abstract class LocalFolderItem implements FolderItem {
|
||||||
checkoutMgr.updateCheckout(checkoutId, checkoutVersion);
|
checkoutMgr.updateCheckout(checkoutId, checkoutVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.store.local;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import ghidra.framework.store.*;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>LocalTextDataItem</code> provides a {@link LocalFolderItem} implementation
|
||||||
|
* which stores text data within the associated propertyFile and without any other data storage.
|
||||||
|
*/
|
||||||
|
public class LocalTextDataItem extends LocalFolderItem implements TextDataItem {
|
||||||
|
|
||||||
|
private static final String TEXT_PROPERTY = "TEXT";
|
||||||
|
private static final String VERSION_CREATE_USER = "CREATE_USER";
|
||||||
|
private static final String VERSION_CREATE_TIME = "CREATE_TIME";
|
||||||
|
private static final String VERSION_CREATE_COMMENT = "CREATE_COMMENT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for an existing local link file item which corresponds to the specified
|
||||||
|
* property file.
|
||||||
|
* @param fileSystem file system
|
||||||
|
* @param propertyFile database property file
|
||||||
|
* @throws IOException if an IO Error occurs
|
||||||
|
*/
|
||||||
|
public LocalTextDataItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
|
||||||
|
throws IOException {
|
||||||
|
super(fileSystem, propertyFile, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new local text data file item.
|
||||||
|
* @param fileSystem file system
|
||||||
|
* @param propertyFile serialized data property file
|
||||||
|
* @param fileID file ID to be associated with new file or null
|
||||||
|
* @param contentType user content type
|
||||||
|
* @param textData text to be stored within associated property file
|
||||||
|
* @throws IOException if an IO Error occurs
|
||||||
|
*/
|
||||||
|
public LocalTextDataItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
|
||||||
|
String fileID, String contentType, String textData) throws IOException {
|
||||||
|
super(fileSystem, propertyFile, false, true);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(contentType)) {
|
||||||
|
abortCreate();
|
||||||
|
throw new IllegalArgumentException("Missing content-type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(textData)) {
|
||||||
|
abortCreate();
|
||||||
|
throw new IllegalArgumentException("Missing text data");
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyFile.putInt(FILE_TYPE, LINK_FILE_TYPE);
|
||||||
|
propertyFile.putBoolean(READ_ONLY, false);
|
||||||
|
propertyFile.putString(CONTENT_TYPE, contentType);
|
||||||
|
if (fileID != null) {
|
||||||
|
propertyFile.setFileID(fileID);
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyFile.putString(TEXT_PROPERTY, textData);
|
||||||
|
|
||||||
|
propertyFile.writeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text data that was stored with this item
|
||||||
|
* @return text data
|
||||||
|
*/
|
||||||
|
public String getTextData() {
|
||||||
|
return propertyFile.getString(TEXT_PROPERTY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long length() throws IOException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCheckout(FolderItem versionedFolderItem, boolean updateItem,
|
||||||
|
TaskMonitor monitor) throws IOException {
|
||||||
|
throw new IOException("Versioning updates not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCheckout(FolderItem item, int checkoutVersion) throws IOException {
|
||||||
|
throw new IOException("Versioning updates not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void deleteMinimumVersion(String user) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Versioning updates not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void deleteCurrentVersion(String user) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Versioning updates not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
|
||||||
|
throw new IOException("Output not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int getMinimumVersion() {
|
||||||
|
return getCurrentVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentVersion() {
|
||||||
|
return 1; // only a single version of the file may exist
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canRecover() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the version info associated with this versioned file. Only a single version is
|
||||||
|
* supported.
|
||||||
|
* @param version version information (only user, create time and comment is retained)
|
||||||
|
* @throws IOException if an IO error occurs
|
||||||
|
*/
|
||||||
|
public void setVersionInfo(Version version) throws IOException {
|
||||||
|
synchronized (fileSystem) {
|
||||||
|
if (!isVersioned()) {
|
||||||
|
throw new UnsupportedOperationException("Versioning not supported");
|
||||||
|
}
|
||||||
|
propertyFile.putString(VERSION_CREATE_USER, version.getUser());
|
||||||
|
propertyFile.putLong(VERSION_CREATE_TIME, version.getCreateTime());
|
||||||
|
propertyFile.putString(VERSION_CREATE_COMMENT, version.getComment());
|
||||||
|
propertyFile.writeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Version[] getVersions() throws IOException {
|
||||||
|
synchronized (fileSystem) {
|
||||||
|
if (!isVersioned) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Non-versioned item does not support getVersions");
|
||||||
|
}
|
||||||
|
String createUser = propertyFile.getString(VERSION_CREATE_USER, "");
|
||||||
|
long createTime = propertyFile.getLong(VERSION_CREATE_TIME, 0);
|
||||||
|
String comment = propertyFile.getString(VERSION_CREATE_COMMENT, null);
|
||||||
|
return new Version[] { new Version(1, createTime, createUser, comment) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,16 +19,13 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import ghidra.framework.store.*;
|
import ghidra.framework.store.*;
|
||||||
import ghidra.util.PropertyFile;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>UnknownFolderItem</code> acts as a LocalFolderItem place-holder for
|
* <code>UnknownFolderItem</code> acts as a LocalFolderItem place-holder for
|
||||||
* items of an unknown type.
|
* items of an unknown type.
|
||||||
*/
|
*/
|
||||||
public class UnknownFolderItem extends LocalFolderItem {
|
public class LocalUnknownFolderItem extends LocalFolderItem implements UnknownFolderItem {
|
||||||
|
|
||||||
public static final String UNKNOWN_CONTENT_TYPE = "Unknown-File";
|
|
||||||
|
|
||||||
private final int fileType;
|
private final int fileType;
|
||||||
|
|
||||||
|
@ -37,7 +34,7 @@ public class UnknownFolderItem extends LocalFolderItem {
|
||||||
* @param fileSystem local file system
|
* @param fileSystem local file system
|
||||||
* @param propertyFile property file associated with this item
|
* @param propertyFile property file associated with this item
|
||||||
*/
|
*/
|
||||||
UnknownFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
|
LocalUnknownFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile) {
|
||||||
super(fileSystem, propertyFile);
|
super(fileSystem, propertyFile);
|
||||||
fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
|
fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
|
||||||
}
|
}
|
||||||
|
@ -55,134 +52,82 @@ public class UnknownFolderItem extends LocalFolderItem {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#updateCheckout(ghidra.framework.store.FolderItem, boolean, ghidra.util.task.TaskMonitor)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCheckout(FolderItem versionedFolderItem, boolean updateItem,
|
public void updateCheckout(FolderItem versionedFolderItem, boolean updateItem,
|
||||||
TaskMonitor monitor) throws IOException {
|
TaskMonitor monitor) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#updateCheckout(ghidra.framework.store.FolderItem, int)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCheckout(FolderItem item, int checkoutVersion) throws IOException {
|
public void updateCheckout(FolderItem item, int checkoutVersion) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#checkout(java.lang.String)
|
|
||||||
*/
|
|
||||||
public synchronized ItemCheckoutStatus checkout(String user) throws IOException {
|
public synchronized ItemCheckoutStatus checkout(String user) throws IOException {
|
||||||
throw new IOException(propertyFile.getName() +
|
throw new IOException(
|
||||||
" may not be checked-out, item may be corrupt");
|
propertyFile.getName() + " may not be checked-out, item may be corrupt");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#terminateCheckout(long)
|
|
||||||
*/
|
|
||||||
public synchronized void terminateCheckout(long checkoutId) {
|
public synchronized void terminateCheckout(long checkoutId) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#clearCheckout()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void clearCheckout() throws IOException {
|
public void clearCheckout() throws IOException {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#setCheckout(long, int, int)
|
|
||||||
*/
|
|
||||||
public void setCheckout(long checkoutId, int checkoutVersion, int localVersion) {
|
public void setCheckout(long checkoutId, int checkoutVersion, int localVersion) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#getCheckout(long)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized ItemCheckoutStatus getCheckout(long checkoutId) throws IOException {
|
public synchronized ItemCheckoutStatus getCheckout(long checkoutId) throws IOException {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#getCheckouts()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized ItemCheckoutStatus[] getCheckouts() throws IOException {
|
public synchronized ItemCheckoutStatus[] getCheckouts() throws IOException {
|
||||||
return new ItemCheckoutStatus[0];
|
return new ItemCheckoutStatus[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#getVersions()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized Version[] getVersions() throws IOException {
|
public synchronized Version[] getVersions() throws IOException {
|
||||||
throw new IOException("History data is unavailable for " + propertyFile.getName());
|
throw new IOException("History data is unavailable for " + propertyFile.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#getContentType()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getContentType() {
|
public String getContentType() {
|
||||||
|
// NOTE: We could get the content type from the property file but we don't want any
|
||||||
|
// attempt to use it
|
||||||
return UNKNOWN_CONTENT_TYPE;
|
return UNKNOWN_CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.local.LocalFolderItem#deleteMinimumVersion(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
void deleteMinimumVersion(String user) throws IOException {
|
void deleteMinimumVersion(String user) throws IOException {
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
|
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.local.LocalFolderItem#deleteCurrentVersion(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
void deleteCurrentVersion(String user) throws IOException {
|
void deleteCurrentVersion(String user) throws IOException {
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
|
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#output(java.io.File, int, ghidra.util.task.TaskMonitor)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
|
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Output not supported for UnknownFolderItems");
|
throw new UnsupportedOperationException("Output not supported for UnknownFolderItems");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.local.LocalFolderItem#getMinimumVersion()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
int getMinimumVersion() throws IOException {
|
int getMinimumVersion() throws IOException {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#getCurrentVersion()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentVersion() {
|
public int getCurrentVersion() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see ghidra.framework.store.FolderItem#canRecover()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRecover() {
|
public boolean canRecover() {
|
||||||
return false;
|
return false;
|