Merge branch 'GP-3551_ghidra1_InternalProjectLinks'

This commit is contained in:
ghidra1 2025-07-09 13:34:56 -04:00
commit 53552616ec
209 changed files with 10096 additions and 3050 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,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;
@ -30,7 +31,8 @@ public class RenameProgramsInProjectScript extends GhidraScript {
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;
} }
@ -41,18 +43,23 @@ public class RenameProgramsInProjectScript extends GhidraScript {
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) {
monitor.checkCancelled();
processDomainFile(domainFile); processDomainFile(domainFile);
} }
DomainFolder[] folders = domainFolder.getFolders(); DomainFolder[] folders = domainFolder.getFolders();
for (DomainFolder folder : folders) { for (DomainFolder folder : folders) {
monitor.checkCancelled();
recurseProjectFolder(folder); 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");

View file

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

View file

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

View file

@ -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>&nbsp;</P> <P>&nbsp;</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.&nbsp;</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&nbsp; <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>&nbsp;</P> <P>&nbsp;</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&nbsp;</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)&nbsp;</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&nbsp;</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)&nbsp;</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>&nbsp;</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://&lt;hostname&gt;[:&lt;port&gt;]/&lt;repository_name&gt;[/&lt;folder_or_file_path&gt;]</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:[/&lt;directory_path&gt;]/&lt;project_name&gt;[?/&lt;folder_or_file_path&gt;]</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1,003 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 + ".");

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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