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

View file

@ -4,9 +4,9 @@
* 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.
@ -116,10 +116,8 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
? view.getTrace().getFixedProgramView(view.getSnap())
: view;
ExporterDialog dialog =
new ExporterDialog(tool, fixed.getDomainFile(), fixed,
getSelectionFromContext(context));
tool.showDialog(dialog);
ExporterDialog.showExporterDialog(tool, fixed.getDomainFile(), fixed,
getSelectionFromContext(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.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Lifespan;
@ -73,7 +74,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
// TODO: Note language and prefer those from the same processor?
// Will get difficult with new OBTR, since I'd need a platform
// 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
implements DomainObjectListener, DomainObjectClosedListener {
@ -212,10 +214,13 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
if (disposed) {
return;
}
if (!Program.class.isAssignableFrom(file.getDomainObjectClass())) {
return;
// Folder-links and program link-files are not handled. Using content type
// 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) {
@ -383,9 +388,9 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
public DomainFile getBestMatch(TraceModule module, long snap, Program currentProgram,
Collection<IndexEntry> entries) {
Address base = module.getBase(snap);
AddressSpace space = base == null
? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
: base.getAddressSpace();
AddressSpace space =
base == null ? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
: base.getAddressSpace();
return getBestMatch(space, module, snap, currentProgram, entries);
}

View file

@ -15,7 +15,7 @@
*/
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.lang.invoke.MethodHandles;
@ -223,9 +223,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
public void targetWithdrawn(Target target) {
Swing.runLater(() -> updateCurrentTarget());
boolean save = isSaveTracesByDefault();
CompletableFuture<Void> flush = save
? waitUnlockedDebounced(target)
: AsyncUtils.nil();
CompletableFuture<Void> flush = save ? waitUnlockedDebounced(target) : AsyncUtils.nil();
flush.thenRunAsync(() -> {
if (!isAutoCloseOnTerminate()) {
return;
@ -416,20 +414,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
protected DataTreeDialog getTraceChooserDialog() {
DomainFileFilter filter = new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return Trace.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false;
}
};
DomainFileFilter filter = new DefaultDomainFileFilter(Trace.class, false);
return new DataTreeDialog(null, OpenTraceAction.NAME, OPEN, filter);
}
@ -454,11 +439,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public void closeDeadTraces() {
checkCloseTraces(targetService == null
? getOpenTraces()
: getOpenTraces().stream()
.filter(t -> targetService.getTarget(t) == null)
.toList(),
checkCloseTraces(targetService == null ? getOpenTraces()
: getOpenTraces().stream().filter(t -> targetService.getTarget(t) == null).toList(),
false);
}
@ -790,8 +772,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
varView.setSnap(snap);
varView.setPlatform(coordinates.getPlatform());
fireLocationEvent(coordinates, cause);
}, cause == ActivationCause.EMU_STATE_EDIT
? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
}, cause == ActivationCause.EMU_STATE_EDIT ? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
: SwingExecutorService.LATER); // Respect event order
}
@ -845,7 +826,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
// TODO: Support upgrading
e = new VersionException(e.getVersionIndicator(), false).combine(e);
VersionExceptionHandler.showVersionError(null, file.getName(), file.getContentType(),
"Open", e);
"Open", false, e);
return null;
}
catch (IOException e) {
@ -1069,10 +1050,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
protected void checkCloseTraces(Collection<Trace> traces, boolean noConfirm) {
List<Target> live =
traces.stream()
.map(t -> targetService.getTarget(t))
.filter(t -> t != null)
.toList();
traces.stream().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
* same thread to avoid a ClosedException.

View file

@ -4,9 +4,9 @@
* 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.
@ -313,7 +313,7 @@ public class DBTraceContentHandler extends DBWithUserDataContentHandler<DBTrace>
@Override
public String getContentTypeDisplayString() {
return "Trace";
return TRACE_CONTENT_TYPE;
}
@Override

View file

@ -4,9 +4,9 @@
* 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.
@ -15,32 +15,15 @@
*/
package ghidra.trace.database;
import java.io.IOException;
import javax.swing.Icon;
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 static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
public static DBTraceLinkContentHandler INSTANCE = new DBTraceLinkContentHandler();
@Override
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);
}
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
@Override
public String getContentType() {

View file

@ -4,9 +4,9 @@
* 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.
@ -15,8 +15,6 @@
*/
package ghidra.machinelearning.functionfinding;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.util.*;
import java.util.stream.Collectors;
@ -35,7 +33,7 @@ import docking.widgets.table.GTable;
import docking.widgets.table.threaded.GThreadedTablePanel;
import docking.widgets.textfield.IntegerTextField;
import ghidra.app.services.ProgramManager;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.DomainFile;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.address.AddressSet;
@ -485,11 +483,7 @@ public class FunctionStartRFParamsDialog extends ReusableDialogComponentProvider
}
private void searchOtherProgram(RandomForestRowObject modelRow) {
DataTreeDialog dtd =
new DataTreeDialog(null, "Select Program", OPEN, f -> {
Class<?> c = f.getDomainObjectClass();
return Program.class.isAssignableFrom(c);
});
ProgramFileChooser dtd = new ProgramFileChooser(null, "Select Program");
dtd.show();
DomainFile dFile = dtd.getDomainFile();
if (dFile == null) {

View file

@ -4,9 +4,9 @@
* 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.
@ -22,6 +22,7 @@ import java.net.URL;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.protocol.ghidra.*;
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
@ -56,7 +57,8 @@ public abstract class IterateRepository {
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
public void processResult(DomainFolder domainFolder, URL url, TaskMonitor m)
@ -76,7 +78,9 @@ public abstract class IterateRepository {
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)
throws IOException, CancelledException {
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// Do not follow folder-links or consider program links to avoid possible duplication of
// file processing. Using content type is the best way to restrict this. If program links
// should be considered "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
// NOTE: linked-folders and linked-files are not currently supported
return; // skip non-program file
}
@ -129,6 +132,7 @@ public abstract class IterateRepository {
Msg.debug(IterateRepository.class, "Processing " + file.getPathname() + "...");
monitor.setMessage("Processing: " + file.getName());
monitor.incrementProgress(1);
// NOTE: The following method invocation will follow all links if presented one
program = (Program) file.getReadOnlyDomainObject(this, -1, 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/Restore_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/ArchiveProject.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/ViewProjectAccessPanel.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/images/AddFunctionsPanel.png||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END|

View file

@ -4,9 +4,9 @@
* 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.
@ -30,7 +30,7 @@ import ghidra.app.script.ImproperUseException;
import ghidra.framework.data.GhidraFile;
import ghidra.framework.data.GhidraFileData;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.*;
import ghidra.framework.store.FolderItem;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.program.model.lang.LanguageDescription;
@ -115,8 +115,8 @@ public class FixLangId extends GhidraScript {
if (langId != null) {
Msg.warn(this, "Changing language ID from '" + record.getString(0) + "' to '" +
langId + "' for program: " + df.getName());
desc = DefaultLanguageService.getLanguageService().getLanguageDescription(
new LanguageID(langId));
desc = DefaultLanguageService.getLanguageService()
.getLanguageDescription(new LanguageID(langId));
long txId = dbh.startTransaction();
try {
record.setString(0, langId);
@ -139,7 +139,10 @@ public class FixLangId extends GhidraScript {
public DomainFile askProgramFile(String title) {
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.close();
domainFile[0] = dtd.getDomainFile();

View file

@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.
@ -20,48 +19,56 @@
import ghidra.app.script.GhidraScript;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramContentHandler;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import java.io.IOException;
public class RenameProgramsInProjectScript extends GhidraScript {
@Override
public void run() throws Exception {
if ( currentProgram != null ) {
popup( "This script should be run from a tool with no open programs" );
return;
}
PluginTool tool = state.getTool();
Project project = tool.getProject();
ProjectData projectData = project.getProjectData();
DomainFolder rootFolder = projectData.getRootFolder();
recurseProjectFolder( rootFolder );
}
private void recurseProjectFolder( DomainFolder domainFolder ) {
DomainFile[] files = domainFolder.getFiles();
for ( DomainFile domainFile : files ) {
processDomainFile( domainFile );
}
DomainFolder[] folders = domainFolder.getFolders();
for ( DomainFolder folder : folders ) {
recurseProjectFolder( folder );
}
}
private void processDomainFile( DomainFile domainFile ) {
String oldName = domainFile.getName();
try {
domainFile.setName( oldName + "_renamed" );
}
catch ( InvalidNameException e ) {
e.printStackTrace();
}
catch ( IOException e ) {
e.printStackTrace();
}
}
@Override
public void run() throws Exception {
if (currentProgram != null) {
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;
}
PluginTool tool = state.getTool();
Project project = tool.getProject();
ProjectData projectData = project.getProjectData();
DomainFolder rootFolder = projectData.getRootFolder();
recurseProjectFolder(rootFolder);
}
private void recurseProjectFolder(DomainFolder domainFolder) throws CancelledException {
DomainFile[] files = domainFolder.getFiles();
for (DomainFile domainFile : files) {
monitor.checkCancelled();
processDomainFile(domainFile);
}
DomainFolder[] folders = domainFolder.getFolders();
for (DomainFolder folder : folders) {
monitor.checkCancelled();
recurseProjectFolder(folder);
}
}
private void processDomainFile(DomainFile domainFile) {
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domainFile.getContentType())) {
return;
}
String oldName = domainFile.getName();
try {
domainFile.setName(oldName + "_renamed");
}
catch (InvalidNameException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -102,6 +102,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
}
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;
for (DomainFile df : folder.getFiles()) {
monitor.checkCancelled();
@ -115,8 +117,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
}
private int listCheckouts(DomainFile df) throws IOException {
if (!df.isVersioned()) {
return 0;
if (!df.isVersioned() || df.isLink()) {
return 0; // ignore non-versioned files and link-files
}
int count = 0;
for (ItemCheckoutStatus checkout : df.getCheckouts()) {

View file

@ -4,9 +4,9 @@
* 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.
@ -27,7 +27,6 @@ public class VersionControl_ResetAll extends GhidraScript {
public VersionControl_ResetAll() {
}
@Override
public void run() throws Exception {
@ -54,7 +53,8 @@ public class VersionControl_ResetAll extends GhidraScript {
if (monitor.isCancelled()) {
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()) ||
!file.isVersioned() || file.getLatestVersion() < 2) {
continue;// skip

View file

@ -60,6 +60,8 @@
<LI><A href="#ProjectDataTable">Data Table</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>
@ -93,8 +95,10 @@
<H2><A name="ActiveProjectPanel"></A>Active Project</H2>
<BLOCKQUOTE>
<P>The Active Project view shows your programs and datatype archives in a tree view or a
table view. The tree view is useful for organizing your files into folders and sub-folders.
<P>The Active Project view shows the various files associated with the current
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
size, processor, or modification date. In either view, you open and perform various
actions on program files or datatype archives.</P>
@ -105,13 +109,23 @@
</CENTER>
<BLOCKQUOTE>
<P>The data tree shows all files in the project orgnanized into folders and sub-folders.
<A href="#FileIcons">Icons for files</A>
indicate whether they are under <A href=
<A href="#FileIcons">Icons for files</A> indicate whether they are under <A href=
"help/topics/VersionControl/project_repository.htm#Versioning">version control</A> and whether
you have the file <A href=
"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>
<P>&nbsp;</P>
<H3>Tree Only Actions</H3>
@ -124,7 +138,7 @@
<P>To create a new folder,</P>
<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>
@ -133,8 +147,6 @@
editing.</LI>
</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>
<H4><A name="Copy"></A><A name="Paste"></A>Copy Folders and Files</H4>
@ -159,7 +171,47 @@
time.&nbsp;</LI>
</OL>
</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>
<BLOCKQUOTE>
@ -182,7 +234,7 @@
</OL>
<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>
<H4>Drag/Drop for Copy</H4>
@ -217,6 +269,9 @@
<LI>Release the mouse button when you get a valid drop target.</LI>
</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>
<P><IMG src="help/shared/note.png" border="0"> If a folder or file
@ -241,6 +296,24 @@
<LI>Right mouse click and choose the <B>Collapse All</B> option.</LI>
</OL>
</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>
@ -421,9 +494,9 @@
<TD style="vertical-align: top; width: 10px;">-<BR>
</TD>
<TD style="vertical-align: top;">A <A href=
<TD style="vertical-align: top;"><A href=
"help/topics/Program/Ghidra_Programs.htm"><SPAN style=
"font-weight: bold;">program</SPAN></A><BR>
"font-weight: bold;">Program</SPAN></A><BR>
</TD>
</TR>
@ -434,12 +507,34 @@
<TD style="vertical-align: top; width: 10px;">-<BR>
</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
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>
</TD>
</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>
</TABLE>
@ -521,6 +616,61 @@
not under version control, exists only on your local machine, and is not visible to
other users.</TD>
</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>
<TD width="25%">Hijacked File</TD>
@ -545,9 +695,44 @@
</TABLE>
</CENTER>
</DIV>
</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>
<H2><A name="ReadOnlyProjectDataPanel"></A>Read-Only Project Data</H2>
@ -697,47 +882,54 @@
<BLOCKQUOTE>
<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.
This is done using a Ghidra URL which references the
file in its local or remote storage location. If the viewed project corresponds to a
viewed repository a remote URL will be used, while other cases will refer to the
locally viewed project. It is possible for links to become broken if the referenced
repository, local project or file location are changed.</P>
corresponding folder or file within your project or to a read-only viewed project.
External links are established using a Ghidra URL which references a
file or folder in its local or remote storage location. An external Ghidra URL will
be used if a link refers to a viewed project or repository. It is possible for internal links to
become broken if the referenced file or folder location has changed (e.g., no longers exists
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>
<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>Select a destination folder in the active project tree.</li>
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.
<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>
<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>
</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).
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.
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
connection password when accessing a linked folder or file.</P>
<P>Within a project file chooser dialog a linked-folder may be expanded in a similar fashion
connection password when accessing an external folder or file link.</P>
<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>
<P><IMG src="help/shared/note.png" border="0"><B>Add to Version Control...</B> is supported
for repository folder and file links only and will be disabled for links to a
local project.</P>
<P><IMG src="help/shared/note.png" border="0">Currently, linked-files only provide access
to the latest file version and do not facilitate access to older file versions.</P>
<P><IMG src="help/shared/note.png" border="0">Currently, external file-links only provide access
to the latest file version and do not facilitate access to older file versions. An external
folder-link will allow access to file versions contained within such a folder.
</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
same file in the viewed project. Hovering the mouse over a linked-file will show the URL
of the linked file or folder. The chain-link icon decoration indicates such a linked
file or folder.</P>
same file in the viewed project.</P>
<CENTER>
<IMG src= "images/LinkOtherProject.png" border="0">
</CENTER>
<P>A folder or file link will show its referenced location with either
same file in the viewed project.</P>
</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

@ -4,9 +4,9 @@
* 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.
@ -133,8 +133,9 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
OPTION_DESCRIPTION_GDT_FILEPATH,
() -> new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER));
options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null,
OPTION_DESCRIPTION_PROJECT_PATH, () -> new ProjectPathChooserEditor(
"Choose Data Type Archive", DATATYPEARCHIVE_PROJECT_FILTER));
OPTION_DESCRIPTION_PROJECT_PATH,
() -> new ProjectPathChooserEditor("Choose Data Type Archive",
new DefaultDomainFileFilter(DataTypeArchive.class, false)));
}
@Override
@ -289,6 +290,4 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
.collect(Collectors.toMap(f -> f.getName(), f -> f));
}
private static final DomainFileFilter DATATYPEARCHIVE_PROJECT_FILTER =
df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
}

View file

@ -4,9 +4,9 @@
* 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.
@ -46,10 +46,6 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
private String title;
private DomainFileFilter filter;
public ProjectPathChooserEditor() {
this(null, null);
}
public ProjectPathChooserEditor(String title, DomainFileFilter filter) {
this.title = title;
this.filter = filter;
@ -127,8 +123,7 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
private void displayFileChooser() {
AtomicReference<String> result = new AtomicReference<>();
DataTreeDialog dataTreeDialog =
new DataTreeDialog(this, title, OPEN, filter);
DataTreeDialog dataTreeDialog = new DataTreeDialog(this, title, OPEN, filter);
dataTreeDialog.addOkActionListener(e -> {
dataTreeDialog.close();
DomainFile df = dataTreeDialog.getDomainFile();

View file

@ -4,9 +4,9 @@
* 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.
@ -92,8 +92,7 @@ class OpenDomainFileTask extends Task {
private boolean isFileOpen() {
List<Archive> dtArchiveList = dtmHandler.getAllArchives();
for (int i = 0; i < dtArchiveList.size(); i++) {
Archive archive = dtArchiveList.get(i);
for (Archive archive : dtArchiveList) {
if (archive instanceof ProjectArchive) {
ProjectArchive projectArchive = (ProjectArchive) archive;
DomainFile archiveDomainFile = projectArchive.getDomainFile();
@ -156,7 +155,7 @@ class OpenDomainFileTask extends Task {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(tool.getToolFrame(), domainFile.getName(),
contentType, "Open", e);
contentType, "Open", false, e);
}
}
@ -179,7 +178,7 @@ class OpenDomainFileTask extends Task {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
"Open", e);
"Open", false, e);
}
catch (CancelledException e) {
// 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 boolean treeDialogCancelled = false;
private DomainFileFilter createArchiveFileFilter;
private DomainFileFilter archiveFileFilter;
private DataTypeIndexer dataTypeIndexer;
private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>();
@ -107,18 +107,7 @@ public class DataTypeManagerHandler {
dataTypeIndexer.addDataTypeManager(builtInDataTypesManager);
openArchives.add(new BuiltInArchive(this, builtInDataTypesManager));
createArchiveFileFilter = new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false;
}
};
archiveFileFilter = new DefaultDomainFileFilter(DataTypeArchive.class, true);
folderListener = new MyFolderListener();
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
@ -1454,7 +1443,7 @@ public class DataTypeManagerHandler {
}
private DataTreeDialog getSaveDialog() {
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, createArchiveFileFilter);
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, archiveFileFilter);
ActionListener listener = event -> {
DomainFolder folder = dialog.getDomainFolder();
@ -1486,7 +1475,7 @@ public class DataTypeManagerHandler {
private CreateDataTypeArchiveDataTreeDialog getCreateDialog() {
CreateDataTypeArchiveDataTreeDialog dialog = new CreateDataTypeArchiveDataTreeDialog(null,
"Create", CREATE, createArchiveFileFilter);
"Create", CREATE, archiveFileFilter);
ActionListener listener = event -> {
DomainFolder folder = dialog.getDomainFolder();
@ -1726,7 +1715,7 @@ public class DataTypeManagerHandler {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, newDomainFile.getName(), contentType,
"Re-open", e);
"Re-open", false, e);
}
catch (CancelledException e) {
throw new AssertException(e);
@ -1766,7 +1755,7 @@ public class DataTypeManagerHandler {
Throwable cause = t.getCause();
if (cause instanceof VersionException) {
VersionExceptionHandler.showVersionError(null, archiveFile.getName(), "Archive",
"open", (VersionException) cause);
"open", false, (VersionException) cause);
}
else {
Msg.showError(plugin, plugin.getProvider().getComponent(), "Open Archive Failed",

View file

@ -4,9 +4,9 @@
* 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.
@ -78,6 +78,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
private JTextField filePathTextField;
private JButton fileChooserButton;
private List<Exporter> applicableExporters;
private GhidraComboBox<Exporter> comboBox;
private final DomainFile domainFile;
private boolean domainObjectWasSupplied;
@ -86,39 +87,82 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private PluginTool tool;
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 domainFile the program to export
*/
public ExporterDialog(PluginTool tool, DomainFile domainFile) {
this(tool, domainFile, null, null);
public static void show(PluginTool tool, DomainFile domainFile) {
showExporterDialog(tool, domainFile, null, null);
}
/**
* 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 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).
*/
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) {
super("Export " + domainFile.getName());
if (!Swing.isSwingThread()) {
throw new RuntimeException("ExporterDialog must be instantiated within Swing thread");
}
this.tool = tool;
this.domainFile = domainFile;
this.domainObject = domainObject;
this.currentSelection = selection;
if (domainObject != null) {
applicableExporters = getApplicableExporters(false);
domainObjectWasSupplied = true;
domainObject.addConsumer(this);
}
else {
domainObject = getDomainObjectIfNeeded(TaskMonitor.DUMMY);
applicableExporters = getApplicableExporters(true);
List<Exporter> applicableDomainFileExporters = getApplicableExporters(false);
domainObject = getDomainObjectIfNeeded(!applicableDomainFileExporters.isEmpty());
applicableExporters = getApplicableExporters(false);
}
addWorkPanel(buildWorkPanel());
@ -133,6 +177,11 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
// need to initialize a few things
selectedFormatChanged();
validate();
if (showNoExporterErrorIfNeeded && hasNoApplicableExporter()) {
Msg.showError(this, tool.getToolFrame(), "Unable to Export",
"No available exporters for content type");
}
}
@Override
@ -305,10 +354,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private Component buildFormatChooser() {
List<Exporter> exporters = getApplicableExporters(false);
comboBox = new GhidraComboBox<>(exporters);
comboBox = new GhidraComboBox<>(applicableExporters);
Exporter defaultExporter = getDefaultExporter(exporters);
Exporter defaultExporter = getDefaultExporter(applicableExporters);
if (defaultExporter != null) {
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
* the domainFile's content class will be used.
* @return list of exporters able to handle content
* the domainFile's content class will be used. The {@code applicableExporters} variable
* is set to the applicable list of exporters.
*/
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
@ -330,15 +378,13 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
}
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
if (exporter.canExportDomainFile(domainFile)) {
return true;
if (domainObject != null) {
return exporter.canExportDomainObject(domainObject);
}
if (domainObject == null) {
return preliminaryCheck
? exporter.canExportDomainObject(domainFile.getDomainObjectClass())
: false;
if (preliminaryCheck) {
return exporter.canExportDomainObject(domainFile.getDomainObjectClass());
}
return exporter.canExportDomainObject(domainObject);
return exporter.canExportDomainFile(domainFile);
}
private Exporter getDefaultExporter(List<Exporter> list) {
@ -410,6 +456,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
setOkEnabled(true);
}
public boolean hasNoApplicableExporter() {
return applicableExporters.isEmpty();
}
private boolean hasOptions() {
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) {
return domainObject;
}
@ -459,7 +509,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
// direct domain file export. This avoids potential upgrade issues and preserves
// database in its current state for those exporters.
boolean doOpen = false;
for (Exporter exporter : getApplicableExporters(true)) {
for (Exporter exporter : applicableExporters) {
if (!exporter.canExportDomainFile(domainFile)) {
doOpen = true;
break;
@ -469,35 +519,28 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
return null;
}
if (SystemUtilities.isEventDispatchThread()) {
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
monitor -> doOpenFile(monitor));
}
else {
doOpenFile(taskMonitor);
}
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
monitor -> doOpenFile(exportPossibleWithoutOpening, monitor));
return domainObject;
}
private void doOpenFile(TaskMonitor monitor) {
private void doOpenFile(boolean exportPossibleWithoutOpening, TaskMonitor monitor) {
showNoExporterErrorIfNeeded = false;
String linkedPrefix = domainFile.isLink() ? "linked-" : "";
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 =
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
domainObject =
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
catch (VersionException e) {
String msg = "Could not open file: " + domainFile.getName() +
"\n\nAvailable export options will be limited.";
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
if (exportPossibleWithoutOpening) {
msg += "\n\nAvailable export options will be limited.";
}
if (e.isUpgradable()) {
msg += "\n\nA data upgrade is required. You may open file" +
"\nin a tool first then Export if a different exporter" + "\nis required.";
msg += "\n\nA " + linkedPrefix +
"content upgrade is required. You may open file in a" +
"\ntool first to complete upgrade then Export if needed.";
}
else {
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);
}
catch (IOException e) {
String msg = "Could not open file: " + domainFile.getName() +
"\n\nAvailable export options will be limited.";
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
if (exportPossibleWithoutOpening) {
msg += "\n\nAvailable export options will be limited.";
}
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
}
catch (CancelledException e) {
@ -552,7 +597,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
boolean exportDomainFile =
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
if (!exportDomainFile && domainFile == null) {
if (!exportDomainFile && (domainFile == null || domainFile.isLink())) {
return;
}

View file

@ -4,9 +4,9 @@
* 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.
@ -70,9 +70,8 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
protected void actionPerformed(NavigatableActionContext context) {
Program program = context.getProgram();
DomainFile domainFile = program.getDomainFile();
ExporterDialog dialog =
new ExporterDialog(tool, domainFile, program, context.getSelection());
tool.showDialog(dialog);
ExporterDialog.showExporterDialog(tool, domainFile, program,
context.getSelection());
}
};
MenuData menuData =
@ -104,8 +103,7 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
@Override
protected void actionPerformed(ProjectDataContext context) {
DomainFile domainFile = context.getSelectedFiles().get(0);
ExporterDialog dialog = new ExporterDialog(tool, domainFile);
tool.showDialog(dialog);
ExporterDialog.show(tool, domainFile);
}
@Override
@ -118,6 +116,10 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
if (selectedFiles.size() != 1) {
return false;
}
DomainFile domainFile = context.getSelectedFiles().get(0);
if (domainFile.isLink() && domainFile.getLinkInfo().isFolderLink()) {
return false;
}
return true;
}
};

View file

@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.gotoquery;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.util.Stack;
import org.apache.commons.lang3.StringUtils;
@ -32,7 +30,7 @@ import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.query.TableService;
import ghidra.framework.cmd.Command;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.ProjectData;
import ghidra.framework.plugintool.PluginTool;
@ -106,8 +104,10 @@ public class GoToHelper {
ExternalLocation externalLoc =
program.getExternalManager().getExternalLocation(externalSym);
// 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
// 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.
// 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);
}
@ -187,10 +187,11 @@ public class GoToHelper {
*
* @param nav Navigatable
* @param externalLoc external location
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist,
* otherwise navigation to the first linkage location will be performed
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist
* 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
* displayed.
* displayed, false if no navigation was performed.
*/
protected boolean goToExternalLinkage(Navigatable nav, ExternalLocation externalLoc,
boolean popupAllowed) {
@ -205,8 +206,14 @@ public class GoToHelper {
NavigationUtils.getExternalLinkageAddresses(program, externalSym.getAddress());
if (externalLinkageAddresses.length == 0) {
if (externalLoc.isFunction()) {
tool.setStatusInfo("Failed to identify external linkage address for " +
externalSym.getName(true) + ". Unable to perform navigation.", true);
// This assume external functions always require linkage location
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;
}
@ -306,7 +313,7 @@ public class GoToHelper {
}
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);
if (domainFile == null || service == null) {
tool.setStatusInfo("Unable to navigate to external location. " +
@ -441,8 +448,8 @@ public class GoToHelper {
return;
}
DataTreeDialog dialog = new DataTreeDialog(null,
"Choose External Program (" + extProgName + ")", OPEN);
ProgramFileChooser dialog =
new ProgramFileChooser(null, "Choose External Program (" + extProgName + ")");
dialog.setSearchText(extProgName);
dialog.setHelpLocation(new HelpLocation("ReferencesPlugin", "ChooseExternalProgram"));
tool.showDialog(dialog);

View file

@ -4,9 +4,9 @@
* 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.
@ -79,16 +79,25 @@ public class AboutProgramPlugin extends Plugin implements ApplicationLevelPlugin
@Override
protected void actionPerformed(ProjectDataContext context) {
DomainFile domainFile = context.getSelectedFiles().get(0);
showAbout(domainFile, domainFile.getMetadata());
Map<String, String> metadata = domainFile.getMetadata();
showAbout(domainFile, metadata);
}
@Override
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(
new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
aboutAction.setPopupMenuData(new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
aboutAction.setEnabled(true);
}

View file

@ -88,6 +88,10 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
return false;
}
if (file.isLink() && file.getLinkInfo().isExternalLink()) {
return false;
}
return file.isInWritableProject() &&
Program.class.isAssignableFrom(file.getDomainObjectClass());
}
@ -105,12 +109,12 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
}
};
setLanguageAction.setPopupMenuData(
new MenuData(new String[] { "Set Language..." }, "Language"));
setLanguageAction
.setPopupMenuData(new MenuData(new String[] { "Set Language..." }, "Language"));
setLanguageAction.setEnabled(true);
setLanguageAction.setHelpLocation(
new HelpLocation("LanguageProviderPlugin", "set language"));
setLanguageAction
.setHelpLocation(new HelpLocation("LanguageProviderPlugin", "set language"));
tool.addAction(setLanguageAction);
}

View file

@ -20,10 +20,10 @@ import java.net.URL;
import java.util.Objects;
import ghidra.framework.data.DomainFileProxy;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
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}.
@ -81,11 +81,19 @@ public class ProgramLocator {
file = domainFile;
}
else {
try {
url = GhidraURL.getNormalizedURL(resolveURL(domainFile));
if (domainFile instanceof LinkedDomainFile linkedFile) {
try {
// Attempt to resolve to actual linked-file to allow for
// direct URL reference
domainFile = linkedFile.getLinkedFile();
}
catch (IOException e) {
Msg.error(this, "Failed to resolve linked-file", e);
}
}
catch (IOException e) {
file = domainFile;
url = domainFile.getLocalProjectURL(null);
if (url == null) {
url = domainFile.getSharedProjectURL(null);
}
}
this.domainFile = file;
@ -177,25 +185,4 @@ public class ProgramLocator {
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

@ -4,9 +4,9 @@
* 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.
@ -41,23 +41,12 @@ class ProgramSaveManager {
private ProgramManager programMgr;
private PluginTool tool;
private boolean treeDialogCancelled;
private DomainFileFilter domainFileFilter;
private DomainFileFilter programFileFilter;
ProgramSaveManager(PluginTool tool, ProgramManager programMgr) {
this.tool = tool;
this.programMgr = programMgr;
domainFileFilter = new DomainFileFilter() {
@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)
}
};
programFileFilter = new DefaultDomainFileFilter(Program.class, true);
}
/**
@ -443,8 +432,7 @@ class ProgramSaveManager {
}
private DataTreeDialog getSaveDialog() {
DataTreeDialog dialog =
new DataTreeDialog(null, "Save As", SAVE, domainFileFilter);
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, programFileFilter);
ActionListener listener = event -> {
DomainFolder folder = dialog.getDomainFolder();

View file

@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.references;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.*;
@ -30,7 +28,7 @@ import javax.swing.event.DocumentListener;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
@ -113,8 +111,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
}
});
editButton = new JButton("Edit");
editButton.setToolTipText("Edit Link to External Program");
editButton = new JButton("Select...");
editButton.setToolTipText("Select External Program");
editButton.addActionListener(new ActionListener() {
@Override
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.
*/
private void popupProgramChooser() {
DataTreeDialog d =
new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
final DataTreeDialog dialog = d;
d.addOkActionListener(new ActionListener() {
ProgramFileChooser dialog = new ProgramFileChooser(this.getParent(), "Choose External Program");
dialog.addOkActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DomainFile df = dialog.getDomainFile();
@ -206,7 +202,7 @@ class EditExternalReferencePanel extends EditReferencePanel {
extLibPath.setText(df.getPathname());
}
});
plugin.getTool().showDialog(d);
plugin.getTool().showDialog(dialog);
}
@Override

View file

@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.references;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.util.*;
@ -34,7 +32,7 @@ import generic.theme.GIcon;
import ghidra.app.cmd.refs.*;
import ghidra.framework.cmd.Command;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.plugintool.ComponentProviderAdapter;
@ -236,8 +234,8 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
private void setExternalProgramAssociation() {
List<String> selectedExternalNames = getSelectedExternalNames();
String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled.
DataTreeDialog dialog = new DataTreeDialog(mainPanel,
"Choose External Program (" + externalName + ")", OPEN);
DataTreeDialog dialog = new ProgramFileChooser(mainPanel,
"Choose External Program (" + externalName + ")", AppInfo.getActiveProject());
dialog.setSearchText(externalName);

View file

@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.symboltree;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.*;
import java.awt.event.ItemListener;
import java.util.Arrays;
@ -36,7 +34,7 @@ import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
import ghidra.app.util.NamespaceUtils;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.*;
import ghidra.program.model.address.Address;
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.
*/
private void popupProgramChooser() {
DataTreeDialog d = new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
final DataTreeDialog dialog = d;
d.addOkActionListener(e -> {
ProgramFileChooser dialog =
new ProgramFileChooser(this.getParent(), "Choose External Program");
dialog.addOkActionListener(e -> {
DomainFile df = dialog.getDomainFile();
if (df == null) {
return;
@ -285,7 +283,7 @@ class EditExternalLocationPanel extends JPanel {
dialog.close();
extLibPathTextField.setText(df.getPathname());
});
DockingWindowManager.showDialog(this, d);
DockingWindowManager.showDialog(this, dialog);
}
private void initialize() {
@ -363,7 +361,8 @@ class EditExternalLocationPanel extends JPanel {
Project project = AppInfo.getActiveProject();
ProjectData projectData = project.getProjectData();
DomainFile file = projectData.getFile(extLibPath);
DomainFile file =
projectData.getFile(extLibPath, ProgramFileChooser.PROGRAM_FILE_FILTER);
if (file == null) {
showInputErr("Cannot find the program for the specified library 'Path' of " +
extLibPath + ".");

View file

@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.symboltree.actions;
import static ghidra.framework.main.DataTreeDialogType.*;
import javax.swing.Icon;
import javax.swing.SwingUtilities;
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.framework.cmd.Command;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.ExternalManager;
@ -82,8 +80,8 @@ public class SetExternalProgramAction extends SymbolTreeContextAction {
ExternalManager externalManager = program.getExternalManager();
final String externalLibraryPath = externalManager.getExternalLibraryPath(externalName);
final DataTreeDialog dialog = new DataTreeDialog(provider.getComponent(),
"Choose External Program (" + externalName + ")", OPEN);
ProgramFileChooser dialog = new ProgramFileChooser(provider.getComponent(),
"Choose External Program (" + externalName + ")");
dialog.setSearchText(externalName);

View file

@ -2827,8 +2827,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
DomainFile choice = loadAskValue(this::parseDomainFile, title);
if (!isRunningHeadless()) {
choice = doAsk(Program.class, title, "", choice, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
// File filter employed limits access to program files within the active project
// 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();
if (dtd.wasCancelled()) {
return null;
@ -2932,8 +2934,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
String message = "";
DomainFile choice = doAsk(DomainFile.class, title, message, existingValue, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
// File filter employed limits access to files within the active project
// 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();
if (dtd.wasCancelled()) {
throw new CancelledException();

View file

@ -4,9 +4,9 @@
* 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.
@ -47,7 +47,8 @@ public class GdtExporter extends Exporter {
@Override
public boolean canExportDomainFile(DomainFile domainFile) {
return canExportDomainObject(domainFile.getDomainObjectClass());
// Avoid exporting link-file itself
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
}
@Override

View file

@ -42,7 +42,8 @@ public class GzfExporter extends Exporter {
@Override
public boolean canExportDomainFile(DomainFile domainFile) {
return canExportDomainObject(domainFile.getDomainObjectClass());
// Avoid exporting link-file itself
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
}
@Override

View file

@ -43,6 +43,7 @@ import ghidra.framework.model.*;
import ghidra.framework.project.DefaultProject;
import ghidra.framework.project.DefaultProjectManager;
import ghidra.framework.protocol.ghidra.*;
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
import ghidra.framework.remote.User;
import ghidra.framework.store.LockException;
import ghidra.framework.store.local.LocalFileSystem;
@ -354,7 +355,10 @@ public class HeadlessAnalyzer {
}
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) {
@ -1338,7 +1342,7 @@ public class HeadlessAnalyzer {
boolean filesProcessed = false;
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.
if (domFile != null &&
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {

View file

@ -4,9 +4,9 @@
* 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.
@ -27,6 +27,7 @@ import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFile;
import ghidra.framework.protocol.ghidra.GhidraURLQuery;
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
import ghidra.framework.protocol.ghidra.GhidraURLResultHandlerAdapter;
import ghidra.framework.remote.User;
import ghidra.framework.store.ExclusiveCheckoutException;
@ -103,13 +104,13 @@ public class ProgramOpener {
AtomicReference<Program> openedProgram = new AtomicReference<>();
try {
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
GhidraURLQuery.queryUrl(ghidraUrl, Program.class, new GhidraURLResultHandlerAdapter() {
@Override
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
Program p = openProgram(locator, domainFile, m); // may return null
openedProgram.set(p);
}
}, monitor);
}, LinkFileControl.FOLLOW_EXTERNAL, monitor);
}
catch (IOException | CancelledException e) {
// IOException reported to user by GhidraURLResultHandlerAdapter
@ -148,7 +149,7 @@ public class ProgramOpener {
}
catch (VersionException e) {
String contentType = domainFile.getContentType();
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", e);
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", false, e);
}
catch (CancelledException e) {
// we don't care, the task has been cancelled
@ -197,7 +198,7 @@ public class ProgramOpener {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
"Open", e);
"Open", false, e);
}
return null;
}

View file

@ -4,9 +4,9 @@
* 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.
@ -129,31 +129,37 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
serviceProvider.getService(ProjectDataService.class);
ProjectData projectData = projectDataService.getProjectData();
// default folder is the root folder
DomainFolder folder = projectData.getRootFolder();
// Get program name and folder from program comment annotation
// handles forward and back slashes and with and without first slash
String programText = getProgramText(annotationParts);
String programName = FilenameUtils.getName(programText);
String path = FilenameUtils.getFullPathNoEndSeparator(programText);
DomainFolder folder;
if (path.length() > 0) {
path = StringUtils.prependIfMissing(FilenameUtils.separatorsToUnix(path), "/");
folder = projectData.getFolder(path);
if (folder == null) {
Msg.showInfo(getClass(), null, "Folder Not Found: " + path,
"Unable to locate folder by the name \"" + path);
return true;
}
}
else {
folder = projectData.getRootFolder();
}
if (folder == null) {
Msg.showInfo(getClass(), null, "No Folder: " + path,
"Unable to locate folder by the name \"" + path);
DomainFile programFile = folder.getFile(programName);
if (programFile == null ||
!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
"Unable to locate program at path \"" + programText +
"\".\nNOTE: File names are case-sensitive.");
return true;
}
DomainFile programFile = findProgramByName(programName, folder);
if (programFile == null) {
Msg.showInfo(getClass(), null, "No Program: " + programName,
"Unable to locate a program by the name \"" + programName +
"\".\nNOTE: Program name is case-sensitive. ");
if (!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
"File exists with incorrect content type. ");
return true;
}
@ -199,7 +205,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
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() +
"'.\nMake sure that the given symbol/address exists.");
if (!programManager.isVisible(program)) {
@ -247,27 +253,6 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
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
public String getDisplayString() {
return "Program";
@ -275,7 +260,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
@Override
public String getPrototypeString() {
return "{@program program_name.exe@symbol_name}";
return "{@program program_path@symbol_name}";
}
@Override

View file

@ -291,16 +291,28 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
else {
domainFile = treePanel.getSelectedDomainFile();
if (domainFile != null) {
folderNameLabel.setText(domainFile.getParent().getPathname());
nameField.setText(domainFile.getName());
domainFolder = domainFile.getParent();
}
else {
domainFolder = treePanel.getSelectedDomainFolder();
if (domainFolder == null) {
domainFolder = project.getProjectData().getRootFolder();
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());
nameField.setText(domainFile.getName());
domainFolder = domainFile.getParent();
}
}
if (domainFile == null) {
if (domainFolder == null) {
domainFolder = treePanel.getSelectedDomainFolder();
if (domainFolder == null) {
domainFolder = project.getProjectData().getRootFolder();
}
}
folderNameLabel.setText(domainFolder.getPathname());
if (nameField.isEditable()) {
if (nameField.getText().length() > 0) {
@ -349,7 +361,9 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
* @param file the file
*/
public void selectDomainFile(DomainFile file) {
Swing.runLater(() -> treePanel.selectDomainFile(file));
if (file != null) {
Swing.runLater(() -> treePanel.selectDomainFile(file));
}
}
@Override
@ -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 {
@Override
public void keyPressed(KeyEvent e) {

View file

@ -4,9 +4,9 @@
* 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.
@ -17,8 +17,7 @@ package ghidra.framework.main;
import java.awt.Component;
import ghidra.framework.model.DomainFileFilter;
import ghidra.framework.model.Project;
import ghidra.framework.model.*;
/**
* 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
* files. Following linked-folders will only be allowed if a type of CHOOSE_FOLDER
* or OPEN is specified. If different behavior is required a filter should
* be specified using the other constructor.
* files and/or folders within the active project only. Broken and external links will not be
* shown. If different behavior is required a filter should be specified using the other
* constructor.
*
* @param parent dialog's parent
* @param title title to use
@ -43,7 +42,7 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
* @throws IllegalArgumentException if invalid type is specified
*/
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 title title to use
* @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
*/
public DataTreeDialog(Component parent, String title, DataTreeDialogType type,
@ -66,7 +67,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
* @param parent dialog's parent
* @param title title to use
* @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
* @throws IllegalArgumentException if invalid type is specified
*/

View file

@ -4,9 +4,9 @@
* 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.
@ -90,9 +90,8 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
*/
public OpenVersionedFileDialog(PluginTool tool, String title, Class<T> domainObjectClass,
List<T> openDomainObjects) {
super(tool.getToolFrame(), title, OPEN, f -> {
return domainObjectClass.isAssignableFrom(f.getDomainObjectClass());
}, AppInfo.getActiveProject());
super(tool.getToolFrame(), title, OPEN,
new DefaultDomainFileFilter(domainObjectClass, false), AppInfo.getActiveProject());
this.tool = tool;
this.domainObjectClass = domainObjectClass;
@ -214,8 +213,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
tabbedPane = new JTabbedPane();
tabbedPane.setName("Tabs");
tabbedPane.add("Project Files", projectFilePanel);
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s",
buildOpenObjectsTable());
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s", buildOpenObjectsTable());
tabbedPane.addChangeListener(e -> {
int selectedTabIndex = tabbedPane.getModel().getSelectedIndex();
@ -254,8 +252,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
openObjectsTable = new GFilterTable<>(new OpenObjectsTableModel());
GTable table = openObjectsTable.getTable();
table.getSelectionModel()
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
openObjectsTable.addSelectionListener(e -> {
setOkEnabled(true);
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

@ -4,9 +4,9 @@
* 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.
@ -19,7 +19,6 @@ import java.awt.Component;
import java.io.File;
import java.util.List;
import docking.widgets.tree.GTreeNode;
import ghidra.app.services.FileImporterService;
import ghidra.app.util.FileOpenDataFlavorHandler;
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()) {
return false;
}
doImport(getDomainFolder(destinationNode), fileList, tool, dataTree);
doImport(DataTree.getRealInternalFolderForNode(destinationNode), fileList, tool, dataTree);
return true;
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -38,9 +38,8 @@ public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
* Linux URL-based file list {@link DataFlavor} to be used during handler registration
* using {@link DataTreeDragNDropHandler#addActiveDataFlavorHandler}.
*/
public static final DataFlavor linuxFileUrlFlavor =
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
"String file URL");
public static final DataFlavor linuxFileUrlFlavor = new DataFlavor(
"application/x-java-serialized-object;class=java.lang.String", "String file URL");
@Override
// 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()) {
return false;
}
doImport(getDomainFolder(destinationNode), files, tool, dataTree);
doImport(DataTree.getRealInternalFolderForNode(destinationNode), files, tool, dataTree);
return true;
}

View file

@ -142,7 +142,7 @@ public class ImporterDialog extends DialogComponentProvider {
*/
public void setDestinationFolder(DomainFolder folder) {
destinationFolder = folder;
folderNameTextField.setText(destinationFolder.toString());
folderNameTextField.setText(destinationFolder.getPathname());
validateFormInput();
}
@ -521,7 +521,7 @@ public class ImporterDialog extends DialogComponentProvider {
String parentPath = FilenameUtils.getFullPathNoEndSeparator(pathName);
String fileOrFolderName = FilenameUtils.getName(pathName);
DomainFolder localDestFolder =
(parentPath != null) ? ProjectDataUtils.lookupDomainPath(destinationFolder, parentPath)
(parentPath != null) ? ProjectDataUtils.getDomainFolder(destinationFolder, parentPath)
: destinationFolder;
if (localDestFolder != null) {
if (isFolder && localDestFolder.getFolder(fileOrFolderName) != null ||

View file

@ -28,6 +28,7 @@ import docking.action.*;
import docking.tool.ToolConstants;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.tree.GTreeNode;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.ListingActionContext;
import ghidra.app.events.ProgramActivatedPluginEvent;
@ -441,17 +442,14 @@ public class ImporterPlugin extends Plugin
}
private static DomainFolder getFolderFromContext(ActionContext context) {
DomainFolder folder = null;
Object contextObj = context.getContextObject();
if (contextObj instanceof DomainFolderNode) {
DomainFolderNode node = (DomainFolderNode) contextObj;
return node.getDomainFolder();
if (contextObj instanceof GTreeNode dataTreeNode) {
folder = DataTree.getRealInternalFolderForNode(dataTreeNode);
}
if (contextObj instanceof DomainFileNode) {
DomainFileNode node = (DomainFileNode) contextObj;
DomainFile domainFile = node.getDomainFile();
return domainFile != null ? domainFile.getParent() : null;
if (folder != null && folder.isInWritableProject()) {
return folder;
}
return AppInfo.getActiveProject().getProjectData().getRootFolder();
}

View file

@ -15,8 +15,7 @@
*/
package ghidra.test;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.*;
@ -628,4 +627,21 @@ public class ToyProgramBuilder extends ProgramBuilder {
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

@ -4,9 +4,9 @@
* 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.
@ -22,10 +22,10 @@ import java.awt.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
@ -52,7 +52,6 @@ import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
@ -365,7 +364,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
click(spyNavigatable, spyServiceProvider, annotatedElement);
assertErrorDialog("No Symbol");
assertErrorDialog("Symbol Not Found");
assertTrue(spyServiceProvider.programOpened(programName));
assertTrue(spyServiceProvider.programClosed(programName));
@ -422,7 +421,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
click(spyNavigatable, spyServiceProvider, annotatedElement);
assertErrorDialog("No Symbol");
assertErrorDialog("Symbol Not Found");
assertTrue(spyServiceProvider.programOpened(programName));
assertTrue(spyServiceProvider.programClosed(programName));
@ -478,7 +477,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
click(spyNavigatable, spyServiceProvider, annotatedElement);
assertErrorDialog("No Program");
assertErrorDialog("Program Not Found");
assertFalse(spyServiceProvider.programOpened(programName));
}
@ -495,8 +494,8 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
// real path
String realPath = "folder1/program_f1_f2.exe";
addFakeProgramByPath(spyServiceProvider, realPath);
String realPath = "/folder1/program_f1_f2.exe";
addFakeProgramByPath(spyServiceProvider, realPath, program);
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
String rawComment = "My comment - " + annotationText;
@ -513,7 +512,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
click(spyNavigatable, spyServiceProvider, annotatedElement);
assertErrorDialog("No Folder");
assertErrorDialog("Folder Not Found");
assertFalse(spyServiceProvider.programOpened(otherProgramPath));
}
@ -525,7 +524,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
SpyServiceProvider spyServiceProvider = new SpyServiceProvider();
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
String annotationText = "{@program " + otherProgramPath + "}";
String rawComment = "My comment - " + annotationText;
@ -557,7 +556,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
Address address = program.getAddressFactory().getAddress(addresstring);
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
String rawComment = "My comment - " + annotationText;
@ -591,7 +590,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
String otherProgramPath = "/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 rawComment = "My comment - " + annotationText;
@ -622,9 +621,9 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
String addresstring = "1001000";
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";
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
String rawComment = "My comment - " + annotationText;
@ -656,8 +655,8 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
String addresstring = "1001000";
Address address = program.getAddressFactory().getAddress(addresstring);
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
String rawComment = "My comment - " + annotationText;
@ -883,30 +882,29 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
return new FieldElement[] { fieldElement };
}
private void addFakeProgramByPath(SpyServiceProvider provider, String path) {
private void addFakeProgramByPath(SpyServiceProvider provider, String path, Program p) {
SpyProjectDataService spyProjectData =
(SpyProjectDataService) provider.getService(ProjectDataService.class);
FakeRootFolder root = spyProjectData.fakeProjectData.fakeRootFolder;
String parentPath = FilenameUtils.getFullPath(path);
String programName = FilenameUtils.getName(path);
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;
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);
String folderPath = "/";
if (ix > 0) {
folderPath = path.substring(0, ix);
}
String programName = path.substring(ix + 1);
try {
parent.createFile(programName, (DomainObject) null, TaskMonitor.DUMMY);
DomainFolder parent = ProjectDataUtils.createDomainFolderPath(root, folderPath);
parent.createFile(programName, p, TaskMonitor.DUMMY);
}
catch (Exception e) {
failWithException("Unable to create a dummy domain file", e);
@ -973,41 +971,50 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
}
@Override
public DomainFolder getFolder(String path) {
return fakeRootFolder.getFolder(path);
public DomainFolder getFolder(String path, DomainFolderFilter filter) {
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 List<TestDummyDomainFolder> folders = CollectionUtils.asList(this);
private List<TestDummyDomainFile> folderFiles =
CollectionUtils.asList(new TestDummyDomainFile(this, OTHER_PROGRAM_NAME));
public FakeRootFolder() {
super(null, "Fake Root Folder");
}
void addFolder(TestDummyDomainFolder f) {
folders.add(f);
files.add(new TestDummyDomainFile(this, OTHER_PROGRAM_NAME, "Program"));
}
@Override
public synchronized DomainFile[] getFiles() {
return folderFiles.toArray(new TestDummyDomainFile[folderFiles.size()]);
}
@Override
public synchronized DomainFolder getFolder(String path) {
for (TestDummyDomainFolder folder : folders) {
String folderPath = folder.getPathname();
if (folderPath.equals(path)) {
return folder;
}
}
return null;
public boolean isInWritableProject() {
return true;
}
}

View file

@ -87,10 +87,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
showFiltered("tN");
JTree tree = getJTree();
List<String> expectedFilteredNames = names.stream()
.filter(s -> s.startsWith("tN"))
.sorted()
.collect(Collectors.toList());
List<String> expectedFilteredNames =
names.stream().filter(s -> s.startsWith("tN")).sorted().collect(Collectors.toList());
TreeModel model = tree.getModel();
GTreeNode root = (GTreeNode) model.getRoot();
@ -424,8 +422,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
private void showFiltered(final String startsWith) {
Swing.runLater(() -> {
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog",
OPEN, f -> f.getName().startsWith(startsWith));
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", OPEN,
f -> f.getName().startsWith(startsWith));
dialog.showComponent();
});
waitForSwing();

View file

@ -732,11 +732,22 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
performAction(selectAction, getTreeActionContext(), true);
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);
int count = 0;
while (it.hasNext()) {
GTreeNode node = it.next();
assertTrue(tree.isPathSelected(node.getTreePath()));
if (tree.isPathSelected(node.getTreePath())) {
++count;
}
else {
assertTrue(node.isRoot());
}
}
assertEquals(7, count);
}
@Test
@ -954,11 +965,12 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
for (TreePath path : paths) {
GTreeNode node = (GTreeNode) path.getLastPathComponent();
if (node instanceof DomainFileNode) {
fileList.add(((DomainFileNode) node).getDomainFile());
if (node instanceof DomainFileNode fileNode) {
// NOTE: File may be a linked-folder. Treatment as folder or file depends on action
fileList.add(fileNode.getDomainFile());
}
else if (node instanceof DomainFolderNode) {
folderList.add(((DomainFolderNode) node).getDomainFolder());
else if (node instanceof DomainFolderNode folderNode) {
folderList.add(folderNode.getDomainFolder());
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -15,9 +15,8 @@
*/
package ghidra.file.formats.android.oat.bundle;
import java.util.*;
import java.io.IOException;
import java.util.*;
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.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -45,8 +45,7 @@ public class FullOatBundle implements OatBundle {
private boolean isLittleEndian;
FullOatBundle(Program oatProgram, OatHeader oatHeader, TaskMonitor monitor,
MessageLog log) {
FullOatBundle(Program oatProgram, OatHeader oatHeader, TaskMonitor monitor, MessageLog log) {
this.oatProgram = oatProgram;
this.oatHeader = oatHeader;
@ -120,12 +119,11 @@ public class FullOatBundle implements OatBundle {
DomainFolder parentFolder = domainFile.getParent();
//first, look in current project for VDEX file....
if (lookInProjectFolder(HeaderType.VDEX, parentFolder,
vdexProgramName, monitor, log)) {
if (lookInProjectFolder(HeaderType.VDEX, parentFolder, vdexProgramName, monitor, log)) {
return;
}
if (lookInProjectFolder(HeaderType.VDEX, parentFolder.getParent(),
vdexProgramName, monitor, log)) {
if (lookInProjectFolder(HeaderType.VDEX, parentFolder.getParent(), vdexProgramName, monitor,
log)) {
return;
}
}
@ -140,8 +138,8 @@ public class FullOatBundle implements OatBundle {
break;
}
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
lookInProjectFolder(HeaderType.DEX, odexApkFolder, file.getName(),
monitor, log);
lookInProjectFolder(HeaderType.DEX, odexApkFolder, file.getName(), monitor,
log);
}
}
}
@ -153,8 +151,8 @@ public class FullOatBundle implements OatBundle {
break;
}
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
lookInProjectFolder(HeaderType.DEX, apkOrJarFolder, file.getName(),
monitor, log);
lookInProjectFolder(HeaderType.DEX, apkOrJarFolder, file.getName(), monitor,
log);
}
}
}
@ -166,8 +164,8 @@ public class FullOatBundle implements OatBundle {
break;
}
if (file.getName().startsWith(CDEX)) {
lookInProjectFolder(HeaderType.CDEX, appVdexFolder, file.getName(),
monitor, log);
lookInProjectFolder(HeaderType.CDEX, appVdexFolder, file.getName(), monitor,
log);
}
}
}
@ -183,12 +181,11 @@ public class FullOatBundle implements OatBundle {
DomainFolder parentFolder = domainFile.getParent();
//first, look in current project for ART file....
if (lookInProjectFolder(HeaderType.ART, parentFolder,
artProgramName, monitor, log)) {
if (lookInProjectFolder(HeaderType.ART, parentFolder, artProgramName, monitor, log)) {
return;
}
if (lookInProjectFolder(HeaderType.ART, parentFolder.getParent(),
artProgramName, monitor, log)) {
if (lookInProjectFolder(HeaderType.ART, parentFolder.getParent(), artProgramName, monitor,
log)) {
return;
}
}
@ -203,14 +200,15 @@ public class FullOatBundle implements OatBundle {
* @param log the message log
*/
private boolean lookInProjectFolder(HeaderType type, DomainFolder parentFolder,
String programName,
TaskMonitor monitor, MessageLog log) {
String programName, TaskMonitor monitor, MessageLog log) {
DomainFile child = parentFolder.getFile(programName);
if (child != null) {
DomainFile file = parentFolder.getFile(programName);
// Constrain to Program files only and not program link-files
if (file != null &&
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
Program program = null;
try {
program = (Program) child.getDomainObject(this, true, true, monitor);
program = (Program) file.getDomainObject(this, true, true, monitor);
ByteProvider provider =
MemoryByteProvider.createProgramHeaderByteProvider(program, false);
return makeHeader(type, programName, provider, monitor);

View file

@ -4,9 +4,9 @@
* 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.
@ -22,6 +22,7 @@ import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.address.*;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.lang.Processor;
@ -295,6 +296,7 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
private DestinationProgramInfo recurseFolder(DomainFolder folder, Address destinationAddress,
ProgramManager programManager, TaskMonitor monitor) {
// NOTE: All folder-links and file-links are ignored
DomainFolder[] folders = folder.getFolders();
for (DomainFolder child : folders) {
if (monitor.isCancelled()) {
@ -311,30 +313,31 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
if (monitor.isCancelled()) {
break;
}
DomainObject domainObject = null;
if (!file.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
continue;
}
Program program = null;
try {
domainObject = file.getDomainObject(this, true /* upgrade */,
program = (Program) file.getDomainObject(this, true /* upgrade */,
false /* do not recover */, monitor);
if (domainObject instanceof Program) {
Program program = (Program) domainObject;
if (program.getMemory().contains(destinationAddress)) {
if (programManager != null) {
programManager.openProgram(program, ProgramManager.OPEN_VISIBLE);//once program is located, open it, so lookup is faster next time!
}
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(destinationAddress);
String symbolName = symbol == null ? null : symbol.getName();
return new DestinationProgramInfo(program.getName(), file.getPathname(),
symbolName);
if (program.getMemory().contains(destinationAddress)) {
if (programManager != null) {
//once program is located, open it, so lookup is faster next time!
programManager.openProgram(program, ProgramManager.OPEN_VISIBLE);
}
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(destinationAddress);
String symbolName = symbol == null ? null : symbol.getName();
return new DestinationProgramInfo(program.getName(), file.getPathname(),
symbolName);
}
}
catch (Exception e) {
Msg.warn(this, e);
}
finally {
if (domainObject != null) {
domainObject.release(this);
if (program != null) {
program.release(this);
}
}
}

View file

@ -118,7 +118,7 @@ public class GFileSystemLoadKernelTask extends Task {
ProjectIndexService projectIndex = ProjectIndexService.getIndexFor(project);
DomainFile existingDF = projectIndex.findFirstByFSRL(file.getFSRL());
if ( existingDF != null && programManager != null ) {
if (existingDF != null && programManager != null) {
programManager.openProgram(existingDF);
return;
}
@ -138,6 +138,9 @@ public class GFileSystemLoadKernelTask extends Task {
AppInfo.getActiveProject().getProjectData().getRootFolder(),
file.getParentFile().getPath());
String fileName = ProjectDataUtils.getUniqueName(folder, program.getName());
if (fileName == null) {
throw new IOException("Unable to find unique name for " + program.getName());
}
GhidraProgramUtilities.markProgramAnalyzed(program);

View file

@ -4,9 +4,9 @@
* 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.
@ -250,12 +250,12 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
}
@Override
public void checkCompatibility(int serverInterfaceVersion) throws RemoteException {
if (serverInterfaceVersion > INTERFACE_VERSION) {
public void checkCompatibility(int minServerInterfaceVersion) throws RemoteException {
if (minServerInterfaceVersion > INTERFACE_VERSION) {
throw new RemoteException(
"Incompatible server interface, a newer Ghidra Server version is required.");
}
else if (serverInterfaceVersion < INTERFACE_VERSION) {
else if (minServerInterfaceVersion < MINIMUM_INTERFACE_VERSION) {
throw new RemoteException(
"Incompatible server interface, the minimum supported Ghidra version is " +
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
public RemoteManagedBufferFileHandle createDatabase(String parentPath, String itemName,
String fileID, int bufferSize, String contentType, String projectPath)

View file

@ -39,7 +39,7 @@ public class RepositoryFile {
private LocalFileSystem fileSystem;
private RepositoryFolder parent;
private String name;
private LocalDatabaseItem databaseItem;
private LocalFolderItem folderItem;
private RepositoryItem repositoryItem;
private boolean deleted = false;
@ -69,11 +69,10 @@ public class RepositoryFile {
if (deleted) {
throw new FileNotFoundException(getPathname() + " not found");
}
if (databaseItem == null) {
if (folderItem == null) {
repositoryItem = null;
LocalFolderItem folderItem = fileSystem.getItem(parent.getPathname(), name);
if (folderItem == null || !folderItem.isVersioned() ||
!(folderItem instanceof LocalDatabaseItem)) {
folderItem = fileSystem.getItem(parent.getPathname(), name);
if (folderItem == null) {
// must build pathname just in case folderItem does not exist
String pathname = parent.getPathname();
if (pathname.length() != 1) {
@ -84,7 +83,6 @@ public class RepositoryFile {
"file is corrupt or unsupported", null);
throw new FileNotFoundException(pathname + " is corrupt or unsupported");
}
this.databaseItem = (LocalDatabaseItem) folderItem;
}
}
}
@ -127,16 +125,33 @@ public class RepositoryFile {
synchronized (fileSystem) {
try {
validate();
if (repositoryItem == null) {
repositoryItem =
new RepositoryItem(parent.getPathname(), name, databaseItem.getFileID(),
RepositoryItem.DATABASE, databaseItem.getContentType(),
databaseItem.getCurrentVersion(), databaseItem.lastModified());
if (repositoryItem == null && folderItem != null) {
String textData = null;
int itemType = -1;
if (folderItem instanceof DatabaseItem) {
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) {
repository.log(getPathname(), "Item failure: " + e.getMessage(), null);
}
if (repository == null) {
repositoryItem = new RepositoryItem(parent.getPathname(), name, null,
RepositoryItem.DATABASE, "INVALID", 0, 0);
RepositoryItem.FILE, "INVALID", 0, 0, null);
}
return repositoryItem;
}
@ -157,9 +172,14 @@ public class RepositoryFile {
synchronized (fileSystem) {
validate();
repository.validateReadPrivilege(user);
if (!(folderItem instanceof LocalDatabaseItem databaseItem)) {
throw new IOException(
"Unsupported operation for " + folderItem.getClass().getSimpleName());
}
LocalManagedBufferFile bf = databaseItem.open(version, minChangeDataVer);
repository.log(getPathname(), "version " +
(version < 0 ? databaseItem.getCurrentVersion() : version) + " opened read-only",
repository.log(
getPathname(), "version " +
(version < 0 ? folderItem.getCurrentVersion() : version) + " opened read-only",
user);
return bf;
}
@ -177,7 +197,11 @@ public class RepositoryFile {
synchronized (fileSystem) {
validate();
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) {
throw new IOException("Illegal checkin");
}
@ -202,7 +226,7 @@ public class RepositoryFile {
synchronized (fileSystem) {
validate();
repository.validateReadPrivilege(user);
return databaseItem.getVersions();
return folderItem.getVersions();
}
}
@ -216,7 +240,7 @@ public class RepositoryFile {
public long length() throws IOException {
synchronized (fileSystem) {
validate();
return databaseItem.length();
return folderItem.length();
}
}
@ -234,7 +258,7 @@ public class RepositoryFile {
User userObj = repository.validateWritePrivilege(user);
if (!userObj.isAdmin()) {
Version[] versions = databaseItem.getVersions();
Version[] versions = folderItem.getVersions();
if (deleteVersion == -1) {
for (Version version : versions) {
if (!user.equals(version.getUser())) {
@ -259,21 +283,13 @@ public class RepositoryFile {
throw new IOException("Only the oldest or latest version may be deleted");
}
}
String oldPath = getPathname();
if (databaseItem == null) {
// forced removal by repo Admin
}
else {
databaseItem.delete(deleteVersion, user);
if (folderItem != null) {
folderItem.delete(deleteVersion, user);
}
deleted = true;
repositoryItem = null;
parent.fileDeleted(this);
RepositoryFile newRf = parent.getFile(name);
if (newRf == null) {
RepositoryManager.log(repository.getName(), oldPath, "file deleted", user);
}
parent = null;
}
}
@ -320,7 +336,7 @@ public class RepositoryFile {
synchronized (fileSystem) {
validate();
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 &&
repositoryItem.getFileID() == null) {
repositoryItem = null; // force refresh since fileID should get reset
@ -340,7 +356,7 @@ public class RepositoryFile {
throws IOException {
synchronized (fileSystem) {
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 {
synchronized (fileSystem) {
validate();
ItemCheckoutStatus coStatus = databaseItem.getCheckout(checkoutId);
ItemCheckoutStatus coStatus = folderItem.getCheckout(checkoutId);
if (coStatus != null) {
User userObj = repository.getUser(user);
if (!userObj.isAdmin() && !coStatus.getUser().equals(user)) {
throw new IOException(
"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) {
validate();
repository.validateReadPrivilege(user);
return databaseItem.getCheckout(checkoutId);
return folderItem.getCheckout(checkoutId);
}
}
@ -393,7 +409,7 @@ public class RepositoryFile {
synchronized (fileSystem) {
validate();
repository.validateReadPrivilege(user);
return databaseItem.getCheckouts();
return folderItem.getCheckouts();
}
}
@ -405,7 +421,7 @@ public class RepositoryFile {
public boolean hasCheckouts() throws IOException {
synchronized (fileSystem) {
validate();
return databaseItem.hasCheckouts();
return folderItem.hasCheckouts();
}
}
@ -417,7 +433,7 @@ public class RepositoryFile {
public boolean isCheckinActive() throws IOException {
synchronized (fileSystem) {
validate();
return databaseItem.isCheckinActive();
return folderItem.isCheckinActive();
}
}
@ -436,7 +452,7 @@ public class RepositoryFile {
void pathChanged() {
synchronized (fileSystem) {
repositoryItem = null;
databaseItem = null;
folderItem = null;
}
}

View file

@ -24,8 +24,7 @@ import org.apache.logging.log4j.Logger;
import db.buffers.LocalManagedBufferFile;
import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.local.LocalFolderItem;
import ghidra.framework.store.local.*;
import ghidra.server.Repository;
import ghidra.server.RepositoryManager;
import ghidra.util.InvalidNameException;
@ -94,20 +93,20 @@ public class RepositoryFolder {
private void init() throws IOException {
String path = getPathname();
String[] names = fileSystem.getFolderNames(path);
for (String name2 : names) {
RepositoryFolder subfolder = new RepositoryFolder(repository, fileSystem, this, name2);
folderMap.put(name2, subfolder);
for (String folderName : names) {
RepositoryFolder subfolder =
new RepositoryFolder(repository, fileSystem, this, folderName);
folderMap.put(folderName, subfolder);
}
names = fileSystem.getItemNames(path);
int badItemCount = 0;
for (String name2 : names) {
LocalFolderItem item = fileSystem.getItem(path, name2);
if (item == null || !(item instanceof DatabaseItem)) {
for (String itemName : names) {
LocalFolderItem item = fileSystem.getItem(path, itemName);
if (item == null || (item instanceof UnknownFolderItem)) {
++badItemCount;
continue;
}
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, name2);
fileMap.put(name2, rf);
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, itemName);
fileMap.put(itemName, rf);
}
if (badItemCount != 0) {
log.error("Repository '" + repository.getName() + "' contains " + badItemCount +
@ -217,7 +216,7 @@ public class RepositoryFolder {
if (fileSystem.fileExists(getPathname(), fileName)) {
try {
LocalFolderItem item = fileSystem.getItem(getPathname(), fileName);
if (item == null || !(item instanceof DatabaseItem)) {
if (item == null) {
log.error("Repository '" + repository.getName() + "' contains bad item: " +
makePathname(getPathname(), fileName));
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.
* @param itemName name of new database
@ -445,4 +479,5 @@ public class RepositoryFolder {
: parentPath;
return path + FileSystem.SEPARATOR + childName;
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -87,6 +87,7 @@
//
//@category Version Tracking
import ghidra.app.script.GhidraScript;
import ghidra.feature.vt.api.db.VTSessionContentHandler;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.api.util.VTOptions;
@ -107,8 +108,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
@Override
public void run() throws Exception {
if(currentProgram == null) {
if (currentProgram == null) {
println("Please open the destination program.");
return;
}
@ -175,8 +176,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
return;
}
Program sourceProgram = (Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded,
false, monitor);
Program sourceProgram =
(Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded, false, monitor);
VTSession session = null;
try {
@ -258,19 +259,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
* @throws CancelledException if cancelled
*/
private boolean hasExistingSession(String name, DomainFolder folder) throws CancelledException {
DomainFile[] files = folder.getFiles();
for (DomainFile file : files) {
monitor.checkCancelled();
if (file.getName().equals(name)) {
if (file.getContentType().equals("VersionTracking")) {
return true;
}
}
}
return false;
DomainFile file = folder.getFile(name);
return file != null && file.getContentType().equals(VTSessionContentHandler.CONTENT_TYPE);
}
/**

View file

@ -333,7 +333,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), type, "open",
e);
false, e);
}
catch (IOException e) {
Msg.showError(this, null, "Can't open " + type + ": " + domainFile.getName(),

View file

@ -4,9 +4,9 @@
* 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.
@ -25,8 +25,8 @@ import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.feature.vt.gui.plugin.VTPlugin;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DefaultDomainFileFilter;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFileFilter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
@ -48,7 +48,7 @@ public class OpenVersionTrackingSessionAction extends DockingAction {
PluginTool tool = controller.getTool();
DataTreeDialog dialog =
new DataTreeDialog(tool.getToolFrame(), "Open Version Tracking Session", OPEN,
new VTDomainFileFilter());
new DefaultDomainFileFilter(VTSession.class, true));
tool.showDialog(dialog);
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

@ -4,9 +4,9 @@
* 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.
@ -217,7 +217,7 @@ public class VTControllerImpl
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), "VT Session",
"open", e);
"open", false, e);
}
catch (IOException e) {
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.Gui;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.*;
import ghidra.util.StringUtilities;
import utility.function.Callback;
@ -230,8 +229,8 @@ public class SessionConfigurationPanel extends JPanel {
JButton button = new BrowseButton();
button.setName("SOURCE_BUTTON");
button.addActionListener(e -> {
DomainFile programFile = VTWizardUtils.chooseDomainFile(SessionConfigurationPanel.this,
"a source program", VTWizardUtils.PROGRAM_FILTER, null);
DomainFile programFile = VTWizardUtils.chooseProgramFile(SessionConfigurationPanel.this,
"a source program", null);
if (programFile != null) {
setSourceFile(programFile);
statusChangedCallback.call();
@ -244,8 +243,8 @@ public class SessionConfigurationPanel extends JPanel {
JButton button = new BrowseButton();
button.setName("DESTINATION_BUTTON");
button.addActionListener(e -> {
DomainFile programFile = VTWizardUtils.chooseDomainFile(SessionConfigurationPanel.this,
"a destination program", VTWizardUtils.PROGRAM_FILTER, null);
DomainFile programFile = VTWizardUtils.chooseProgramFile(SessionConfigurationPanel.this,
"a destination program", null);
if (programFile != null) {
setDestinationFile(programFile);
statusChangedCallback.call();

View file

@ -25,8 +25,7 @@ import docking.widgets.OptionDialog;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.gui.task.SaveTask;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFileFilter;
import ghidra.framework.model.*;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities;
import ghidra.util.task.TaskLauncher;
@ -37,29 +36,13 @@ public class VTWizardUtils {
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 boolean accept(DomainFile df) {
return VTSession.class.isAssignableFrom(df.getDomainObjectClass());
}
@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);
public static DomainFile chooseProgramFile(Component parent, String domainIdentifier,
DomainFile fileToSelect) {
final DataTreeDialog dataTreeDialog = new DataTreeDialog(parent,
"Choose " + domainIdentifier, OPEN, new DefaultDomainFileFilter(Program.class, true));
final DomainFileBox box = new DomainFileBox();
dataTreeDialog.addOkActionListener(new ActionListener() {
@Override

View file

@ -4,9 +4,9 @@
* 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.
@ -34,7 +34,7 @@ import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.StubMemory;
import ghidra.program.model.symbol.*;
public class VTBaseTestCase extends AbstractGenericTest {
public abstract class VTBaseTestCase extends AbstractGenericTest {
private DomainFile sourceDomainFile = new TestDummyDomainFile(null, "SourceDomainFile") {
@Override

View file

@ -448,7 +448,14 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
int count = 1;
for (GTreeNode child : children) {
monitor.checkCancelled();
count += child.loadAll(monitor);
if (child.isAutoExpandPermitted()) {
// count the child and its children
count += child.loadAll(monitor);
}
else {
// count just the child
++count;
}
monitor.incrementProgress(1);
}
return count;

View file

@ -4,9 +4,9 @@
* 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.
@ -22,6 +22,9 @@ import docking.widgets.tree.GTreeNode;
/**
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
* 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> {
private Queue<GTreeNode> nodeQueue = new LinkedList<GTreeNode>();
@ -39,7 +42,7 @@ public class BreadthFirstIterator implements Iterator<GTreeNode> {
@Override
public GTreeNode next() {
lastNode = nodeQueue.poll();
if (lastNode != null) {
if (lastNode != null && lastNode.isAutoExpandPermitted()) {
List<GTreeNode> children = lastNode.getChildren();
nodeQueue.addAll(children);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -25,6 +25,9 @@ import docking.widgets.tree.GTreeNode;
/**
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
* 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> {
private Stack<Iterator<GTreeNode>> stack = new Stack<>();
@ -49,7 +52,7 @@ public class DepthFirstIterator implements Iterator<GTreeNode> {
it = stack.pop();
}
lastNode = it.next();
if (lastNode.getChildCount() > 0) {
if (lastNode.isAutoExpandPermitted() && lastNode.getChildCount() > 0) {
if (it.hasNext()) {
stack.push(it);
}

View file

@ -81,6 +81,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
/**
* Returns true if connection recently was lost unexpectedly
* @return true if connection recently was lost unexpectedly
*/
public boolean hadUnexpectedDisconnect() {
return unexpectedDisconnect;
@ -151,6 +152,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
/**
* Returns true if connected.
* @return true if connected.
*/
public boolean isConnected() {
return repository != null;
@ -177,7 +179,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
if (repository == null) {
serverAdapter.connect(); // may cause auto-reconnect of repository
}
if (repository == null) {
if (repository == null && serverAdapter.isConnected()) {
repository = serverAdapter.getRepositoryHandle(name);
unexpectedDisconnect = false;
if (repository == null) {
@ -193,8 +195,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
/**
* Event reader for change dispatcher.
* @return
* @throws IOException
* @return events
* @throws InterruptedIOException if repository handle is closed
*/
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() {
return name;
}
/**
* Returns server adapter
* Get the associated server adapter
* @return server adapter
*/
public RepositoryServerAdapter getServer() {
return serverAdapter;
}
/**
* Returns server information
* Returns associated server information
* @return server information
*/
public ServerInfo 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 NotConnectedException if server/repository connection is down (user already informed)
* @throws IOException if an IO error occurs
* @see ghidra.framework.remote.RemoteRepositoryHandle#getUser()
*/
public User getUser() throws IOException {
@ -305,7 +311,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
/**
* @return true if anonymous access allowed by this repository
* @throws IOException
* @throws IOException if an IO error occurs
*/
public boolean anonymousAccessAllowed() throws IOException {
synchronized (serverAdapter) {
@ -323,10 +329,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
}
/**
* Returns list of repository users.
* @throws IOException
* Returns list of repository users with repository access permission
* @return return users with repository access permission
* @throws UserAccessException user no longer has any permission to use repository.
* @throws NotConnectedException if server/repository connection is down (user already informed)
* @throws IOException if an IO error occurs
* @see RemoteRepositoryHandle#getUserList()
*/
public User[] getUserList() throws IOException {
@ -345,10 +352,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
}
/**
* Returns list of all users known to server.
* @throws IOException
* Returns list of all user names known to server.
* @return list of all user names known to server.
* @throws UserAccessException user no longer has any permission to use repository.
* @throws NotConnectedException if server/repository connection is down (user already informed)
* @throws IOException if an IO error occurs
* @see RemoteRepositoryHandle#getServerUserList()
*/
public String[] getServerUserList() throws IOException {
@ -371,8 +379,8 @@ public class RepositoryAdapter implements RemoteAdapterListener {
* @param users list of user and access permissions.
* @param anonymousAccessAllowed true to permit anonymous access (also requires anonymous
* access to be enabled for server)
* @throws UserAccessException
* @throws IOException
* @throws UserAccessException user is not a repository Admin
* @throws IOException if an IO error occurs
* @throws NotConnectedException if server/repository connection is down (user already informed)
* @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)
*/
public ManagedBufferFileAdapter createDatabase(String parentPath, String itemName,
@ -530,8 +567,8 @@ public class RepositoryAdapter implements RemoteAdapterListener {
/**
* Convert UnmarshalException into UnsupportedOperationException
* @param e
* @throws UnsupportedOperationException
* @param e IOException to be converted if appropriate
* @throws UnsupportedOperationException unsupported operation exception
*/
private void checkUnmarshalException(IOException e, String operation)
throws UnsupportedOperationException {
@ -931,5 +968,4 @@ public class RepositoryAdapter implements RemoteAdapterListener {
public int getOpenFileHandleCount() {
return openFileHandleCount;
}
}

View file

@ -168,13 +168,14 @@ class ServerConnectTask extends Task {
monitor.setCancelEnabled(false);
monitor.setMessage("Connecting...");
Registry reg =
LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
new SslRMIClientSocketFactory());
Registry reg = LocateRegistry.getRegistry(server.getServerName(),
server.getPortNumber(), new SslRMIClientSocketFactory());
checkServerBindNames(reg);
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) {
throw new IOException(e.getMessage());
@ -237,8 +238,7 @@ class ServerConnectTask extends Task {
* @throws LoginException login failure
*/
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
TaskMonitor monitor)
throws IOException, LoginException, CancelledException {
TaskMonitor monitor) throws IOException, LoginException, CancelledException {
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
@ -296,7 +296,8 @@ class ServerConnectTask extends Task {
"Client PKI certificate has not been installed");
}
if (ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate()) {
if (ApplicationKeyManagerFactory
.usingGeneratedSelfSignedCertificate()) {
Msg.warn(this,
"Server connect - client is using self-signed PKI certificate");
}
@ -394,7 +395,7 @@ class ServerConnectTask extends Task {
monitor.setCancelEnabled(true);
monitor.setMessage("Checking Server Liveness...");
// Perform simple socket test connection with short timeout to verify connectivity.
try (Socket socket = new FastConnectionFailSocket(serverName, sslRmiPort);
ConnectCancelledListener cancelListener =

View file

@ -4,9 +4,9 @@
* 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.
@ -50,8 +50,20 @@ public interface GhidraServerHandle extends Remote {
* - 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
* 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

View file

@ -4,9 +4,9 @@
* 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.
@ -70,6 +70,10 @@ public interface RemoteRepositoryHandle extends RepositoryHandle, Remote {
int bufferSize, String contentType, String projectPath)
throws IOException, InvalidNameException;
@Override
void createTextDataFile(String parentPath, String itemName, String fileID, String contentType,
String textData, String comment) throws InvalidNameException, IOException;
@Override
ManagedBufferFileHandle openDatabase(String parentPath, String itemName, int version,
int minChangeDataVer) throws IOException;

View file

@ -4,9 +4,9 @@
* 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.
@ -123,6 +123,21 @@ public interface RepositoryHandle {
*/
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.
* @param parentPath parent folder path
@ -138,8 +153,8 @@ public interface RepositoryHandle {
* @throws InvalidNameException if itemName or parentPath contains invalid characters
*/
ManagedBufferFileHandle createDatabase(String parentPath, String itemName, String fileID,
int bufferSize, String contentType, String projectPath) throws IOException,
InvalidNameException;
int bufferSize, String contentType, String projectPath)
throws IOException, InvalidNameException;
/**
* 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 IOException if an IO error occurs
*/
void moveItem(String oldParentPath, String newParentPath, String oldItemName, String newItemName)
throws InvalidNameException, IOException;
void moveItem(String oldParentPath, String newParentPath, String oldItemName,
String newItemName) throws InvalidNameException, IOException;
/**
* Perform a checkout on the specified item.

View file

@ -4,9 +4,9 @@
* 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.
@ -15,9 +15,12 @@
*/
package ghidra.framework.remote;
import ghidra.framework.store.FileSystem;
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
@ -25,18 +28,36 @@ import java.io.IOException;
*/
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 int FILE = 1;
public final static int DATABASE = 2;
private static final byte SERIALIZATION_SCHEMA_VERSION = 1;
protected String folderPath;
protected String itemName;
protected String fileID;
protected int itemType;
protected String contentType;
protected int version;
protected long versionTime;
public final static int FILE = 1; // DataFileItem (not yet supported)
public final static int DATABASE = 2; // DatabaseItem
public final static int TEXT_DATA_FILE = 3; // TextDataItem
//
// Client use can support reading from older server which presents serialVersionUID==2
//
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
@ -53,9 +74,10 @@ public class RepositoryItem implements java.io.Serializable {
* @param contentType content type associated with item
* @param version repository item version or -1 if versioning not supported
* @param versionTime version creation time
* @param textData related text data (may be null)
*/
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.itemName = itemName;
this.fileID = fileID;
@ -63,6 +85,7 @@ public class RepositoryItem implements java.io.Serializable {
this.contentType = contentType;
this.version = version;
this.versionTime = versionTime;
this.textData = textData;
}
/**
@ -71,6 +94,7 @@ public class RepositoryItem implements java.io.Serializable {
* @throws IOException if an IO error occurs
*/
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.writeLong(serialVersionUID);
out.writeUTF(folderPath);
out.writeUTF(itemName);
@ -79,6 +103,12 @@ public class RepositoryItem implements java.io.Serializable {
out.writeUTF(contentType != null ? contentType : "");
out.writeInt(version);
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 ClassNotFoundException if unrecognized serialVersionUID detected
*/
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
long serialVersion = in.readLong();
if (serialVersion != serialVersionUID) {
throw new ClassNotFoundException("Unsupported version of RepositoryItemStatus");
throw new ClassNotFoundException("Unsupported version of RepositoryItem");
}
folderPath = in.readUTF();
itemName = in.readUTF();
@ -106,6 +136,31 @@ public class RepositoryItem implements java.io.Serializable {
}
version = in.readInt();
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;
}
/**
* Get related text data
* @return text data or null
*/
public String getTextData() {
return textData;
}
}

View file

@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.
@ -24,19 +23,19 @@ import java.io.OutputStream;
* <code>DataFileItem</code> corresponds to a private serialized
* data file within a FileSystem. Methods are provided for opening
* the underlying file as an input or output stream.
* <br>
* <P>
* NOTE: The use of DataFile is not encouraged and is not fully
* supported.
*/
public interface DataFileItem extends FolderItem {
/**
* Open the current version of this item for reading.
* @return input stream
* @throws FileNotFoundException
*/
InputStream getInputStream() throws FileNotFoundException;
/**
* Open a new version of this item for writing.
* @return output stream.
@ -50,5 +49,5 @@ public interface DataFileItem extends FolderItem {
* @throws FileNotFoundException
*/
InputStream getInputStream(int version) throws FileNotFoundException;
}

View file

@ -16,10 +16,13 @@
package ghidra.framework.store;
import java.io.*;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import db.buffers.BufferFile;
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.exception.*;
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
* this will correspond to the name used during login/authentication. A null value may
* 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
* modifying folder items.
* {@return true if the file-system requires check-outs when
* modifying folder items.}
*/
public boolean isVersioned();
/**
* Returns true if file-system is on-line.
* {@return true if file-system is on-line.}
*/
public boolean isOnline();
/**
* Returns true if file-system is read-only.
* @throws IOException
* {@return true if file-system is read-only.}
* @throws IOException if IO error occurs
*/
public boolean isReadOnly() throws IOException;
/**
* Returns the number of folder items contained within this file-system.
* @throws IOException
* {@return the number of folder items contained within this file-system.}
* @throws IOException if an IO error occurs
* @throws UnsupportedOperationException if file-system does not support this operation
*/
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.
* @return a list of folder item names.
* @throws IOException
* @throws IOException if an IO error occurs
*/
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
* while storage was not found. An {@link UnknownFolderItem} may be returned if unsupported
* item storage encountered.
* @throws IOException
* @throws IOException if an IO error occurs
*/
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.
* @param folderPath folder path
* @return subfolders names
* @throws FileNotFoundException if folder path does not exist.
* @throws IOException if IO error occurs.
*/
@ -122,6 +127,16 @@ public interface FileSystem {
public void createFolder(String parentPath, String folderName)
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
* of the specified BufferFile.
@ -162,8 +177,7 @@ public interface FileSystem {
* @return an empty BufferFile open for read-write.
* @throws FileNotFoundException thrown if parent folder does not exist.
* @throws DuplicateFileException if a folder item exists with this name
* @throws InvalidNameException if the name does not have
* all alphanumerics
* @throws InvalidNameException if the name has illegal characters.
* @throws IOException if an IO error occurs.
*/
public ManagedBufferFile createDatabase(String parentPath, String name, String fileID,
@ -182,7 +196,6 @@ public interface FileSystem {
* @return new data file
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
* @throws InvalidNameException if the name has illegal characters.
* all alphanumerics
* @throws IOException if an IO error occurs.
* @throws CancelledException if cancelled by monitor
*/
@ -190,6 +203,23 @@ public interface FileSystem {
String comment, String contentType, TaskMonitor monitor)
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.
* 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.
* @param folderPath path of folder containing the item.
* @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 DuplicateFileException if item with the same name exists within the new parent folder.
* @throws FileInUseException if the item is in-use or checked-out
@ -263,14 +294,14 @@ public interface FileSystem {
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.
*/
public void addFileSystemListener(FileSystemListener listener);
/**
* Removes the listener from being notified of file system changes.
* @param listener
* Removes a file system listener from being notified of file system changes.
* @param listener file system listener
*/
public void removeFileSystemListener(FileSystemListener listener);
@ -283,7 +314,7 @@ public interface FileSystem {
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 name the name of the file to check for existence.
* @throws IOException if an IO error occurs.
@ -291,7 +322,7 @@ public interface FileSystem {
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();
@ -300,4 +331,58 @@ public interface FileSystem {
*/
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

@ -4,9 +4,9 @@
* 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.
@ -43,6 +43,11 @@ public interface FolderItem {
*/
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.
*/

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

@ -4,9 +4,9 @@
* 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.
@ -39,7 +39,7 @@ import utilities.util.FileUtilities;
/**
* <code>PackedDatabase</code> provides a packed form of Database
* which compresses a single version into a file.
* <br>
* <P>
* When opening a packed database, a PackedDBHandle is returned
* after first expanding the file into a temporary Database.
*/
@ -114,7 +114,7 @@ public class PackedDatabase extends Database {
* @throws CancelledException is unpack is cancelled
* @throws IOException if IO error occurs
*/
PackedDatabase(CachedDB cachedDb, ResourceFile packedDbFile, LockFile packedDbLock,
PackedDatabase(CachedDB cachedDb, ResourceFile packedDbFile, LockFile packedDbLock,
TaskMonitor monitor) throws CancelledException, IOException {
super(cachedDb.dbDir, null, false);
this.packedDbFile = packedDbFile;
@ -276,8 +276,8 @@ public class PackedDatabase extends Database {
* @throws IOException if IO error occurs
* @throws CancelledException if unpack/open is cancelled
*/
public static synchronized PackedDatabase getPackedDatabase(ResourceFile packedDbFile, boolean neverCache,
TaskMonitor monitor) throws IOException, CancelledException {
public static synchronized PackedDatabase getPackedDatabase(ResourceFile packedDbFile,
boolean neverCache, TaskMonitor monitor) throws IOException, CancelledException {
if (!neverCache && PackedDatabaseCache.isEnabled()) {
try {
return PackedDatabaseCache.getCache().getCachedDB(packedDbFile, monitor);
@ -633,7 +633,7 @@ public class PackedDatabase extends Database {
tmpFile = Application.createTempFile("pack", ".tmp");
tmpFile.delete();
dbh.saveAs(tmpFile, false, monitor);
try (InputStream itemIn = new BufferedInputStream(new FileInputStream(tmpFile))){
try (InputStream itemIn = new BufferedInputStream(new FileInputStream(tmpFile))) {
ItemSerializer.outputItem(itemName, contentType, FolderItem.DATABASE_FILE_TYPE,
tmpFile.length(), itemIn, outputFile, 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 name = pfile.getName();
@ -832,7 +832,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
catch (NotFoundException e) {
// 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();
for (Item item : folder.items.values()) {
ItemStorage itemStorage = item.itemStorage;
PropertyFile pfile = item.itemStorage.getPropertyFile();
ItemPropertyFile pfile = item.itemStorage.getPropertyFile();
pfile.moveTo(itemStorage.dir, itemStorage.storageName, newFolderPath,
itemStorage.itemName);
itemStorage.folderPath = newFolderPath;
@ -1236,7 +1236,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
folder = getFolder(folderPath, GetFolderOption.READ_ONLY);
if (folder.parent.folders.get(newFolderName) != null) {
throw new DuplicateFileException(
parentPath + SEPARATOR + newFolderName + " already exists.");
getPath(parentPath, newFolderName) + " already exists.");
}
indexJournal.moveFolder(folderPath, getPath(parentPath, newFolderName));
@ -1462,7 +1462,6 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
}
private void replayJournal() throws IndexReadException {
Msg.info(this, "restoring data storage index...");
int lineNum = 0;
BufferedReader journalReader = null;
try {
@ -1778,7 +1777,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
}
@Override
PropertyFile getPropertyFile() throws IOException {
ItemPropertyFile getPropertyFile() throws IOException {
return new IndexedPropertyFile(dir, storageName, folderPath, itemName);
}
}

View file

@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.
@ -16,59 +15,68 @@
*/
package ghidra.framework.store.local;
import ghidra.framework.store.FileSystem;
import ghidra.util.PropertyFile;
import ghidra.util.exception.DuplicateFileException;
import java.io.*;
public class IndexedPropertyFile extends PropertyFile {
import ghidra.util.exception.DuplicateFileException;
public final static String NAME_PROPERTY = "NAME";
public final static String PARENT_PATH_PROPERTY = "PARENT";
public class IndexedPropertyFile extends ItemPropertyFile {
protected static final String NAME_PROPERTY = "NAME";
protected static final String PARENT_PATH_PROPERTY = "PARENT";
/**
* 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 storageName stored property file name (without extension)
* @param parentPath path to parent
* @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)
throws IOException {
super(dir, storageName, parentPath, name);
// if (exists() &&
// (!name.equals(getString(NAME_PROPERTY, null)) || !parentPath.equals(getString(
// PARENT_PATH_PROPERTY, null)))) {
// throw new AssertException();
// }
putString(NAME_PROPERTY, name);
putString(PARENT_PATH_PROPERTY, parentPath);
if (contains(NAME_PROPERTY) && contains(PARENT_PATH_PROPERTY)) {
this.name = getString(NAME_PROPERTY, name);
this.parentPath = getString(PARENT_PATH_PROPERTY, parentPath);
}
else {
// new property file
putString(NAME_PROPERTY, name);
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 storageName stored property file name (without extension)
* @throws FileNotFoundException if property file does not exist
* @throws InvalidObjectException if a file parse error occurs
* @throws IOException if error occurs reading property file
*/
public IndexedPropertyFile(File dir, String storageName) throws IOException {
super(dir, storageName, FileSystem.SEPARATOR, storageName);
super(dir, storageName, null, null);
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) {
throw new IOException("Invalid indexed property file: " + propertyFile);
}
}
/**
* Construct an existing PropertyFile.
* @param file
* Construct a existing PropertyFile.
* This constructor uses property values for NAME and PARENT path.
* @param file property file
* @throws FileNotFoundException if property file does not exist
* @throws InvalidObjectException if a file parse error occurs
* @throws IOException if error occurs reading property file
*/
public IndexedPropertyFile(File file) throws IOException {
@ -82,27 +90,17 @@ public class IndexedPropertyFile extends PropertyFile {
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
public void moveTo(File newParent, String newStorageName, String newParentPath, String newName)
throws DuplicateFileException, IOException {
String oldName = name;
String oldParentPath = parentPath;
super.moveTo(newParent, newStorageName, newParentPath, newName);
// if (!parentPath.equals(newParentPath)) {
// throw new AssertException();
// }
// if (!name.equals(newName)) {
// throw new AssertException();
// }
putString(NAME_PROPERTY, newName);
putString(PARENT_PATH_PROPERTY, newParentPath);
writeState();
if (!newParentPath.equals(oldParentPath) || !newName.equals(oldName)) {
putString(NAME_PROPERTY, name);
putString(PARENT_PATH_PROPERTY, parentPath);
writeState();
}
}
}

View file

@ -19,7 +19,6 @@ import java.io.*;
import java.util.HashMap;
import ghidra.util.Msg;
import ghidra.util.PropertyFile;
import ghidra.util.exception.NotFoundException;
/**
@ -94,7 +93,7 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
}
@Override
protected synchronized void fileIdChanged(PropertyFile pfile, String oldFileId)
protected synchronized void fileIdChanged(ItemPropertyFile pfile, String oldFileId)
throws IOException {
indexJournal.open();
try {
@ -143,12 +142,19 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
if (item == null) {
return null;
}
ItemStorage itemStorage = item.itemStorage;
try {
PropertyFile propertyFile = item.itemStorage.getPropertyFile();
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
if (propertyFile.exists()) {
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) {
// 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

@ -4,9 +4,9 @@
* 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.
@ -17,27 +17,38 @@ package ghidra.framework.store.local;
import ghidra.framework.store.DataFileItem;
import ghidra.framework.store.FolderItem;
import ghidra.util.PropertyFile;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor;
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
* a non-versioned file-system only.
* <p>
* This item utilizes a data directory for storing the serialized
* 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 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);
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 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).
@ -61,9 +72,9 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
* @throws IOException if an IO Error occurs
* @throws CancelledException if monitor cancels operation
*/
public LocalDataFile(LocalFileSystem fileSystem, PropertyFile propertyFile,
InputStream istream, String contentType, TaskMonitor monitor) throws IOException,
CancelledException {
public LocalDataFileItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
InputStream istream, String contentType, TaskMonitor monitor)
throws IOException, CancelledException {
super(fileSystem, propertyFile, true, true);
if (fileSystem.isVersioned()) {
@ -71,6 +82,11 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
throw new UnsupportedOperationException("Versioning not yet supported for DataFiles");
}
if (StringUtils.isBlank(contentType)) {
abortCreate();
throw new IllegalArgumentException("Missing content-type");
}
File dataFile = getDataFile();
if (dataFile.exists()) {
throw new DuplicateFileException(getName() + " already exists.");

View file

@ -4,9 +4,9 @@
* 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.
@ -18,10 +18,13 @@ package ghidra.framework.store.local;
import java.io.File;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import db.buffers.*;
import ghidra.framework.store.*;
import ghidra.framework.store.db.*;
import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.CancelledException;
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
* @throws IOException
*/
private LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, boolean create)
throws IOException {
private LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
boolean create) throws IOException {
super(fileSystem, propertyFile, true, create);
if (isVersioned) {
versionedDbListener = new LocalVersionedDbListener();
@ -63,7 +66,8 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
* @param fileSystem file system
* @param propertyFile database property file
*/
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile) throws IOException {
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
throws IOException {
super(fileSystem, propertyFile, true, false);
if (isVersioned) {
@ -94,11 +98,16 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
* @throws IOException if error occurs
* @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,
TaskMonitor monitor, String user) throws IOException, CancelledException {
super(fileSystem, propertyFile, true, true);
if (StringUtils.isBlank(contentType)) {
abortCreate();
throw new IllegalArgumentException("Missing content-type");
}
boolean success = false;
long checkoutId = DEFAULT_CHECKOUT_ID;
try {
@ -154,7 +163,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
* @throws IOException if error occurs
* @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)
throws IOException, CancelledException {
super(fileSystem, propertyFile, true, true);
@ -222,7 +231,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
* @throws IOException if error occurs
*/
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 {
final LocalDatabaseItem dbItem = new LocalDatabaseItem(fileSystem, propertyFile, true);
@ -257,6 +266,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
db.setSynchronizationObject(dbItem.fileSystem);
dbItem.privateDb = (PrivateDatabase) db;
}
dbItem.log("file created", user);
dbItem.fireItemCreated();
}
}

View file

@ -82,11 +82,12 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Construct a local filesystem for existing data
* @param rootPath
* @param create
* @param isVersioned
* @param readOnly
* @param enableAsyncronousDispatching
* @param rootPath filesystem root directory (the directory must exist and must not have any
* contents if {@code create} is true)
* @param create true if creating new filesystem from the empty directory at rootPath
* @param isVersioned true if creating a versioned filesystem
* @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
* @throws FileNotFoundException if specified rootPath does not exist
* @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);
}
if (create) {
// if (isCreateMangledFileSystemEnabled()) {
// return new MangledLocalFileSystem(rootPath, isVersioned, readOnly,
// enableAsyncronousDispatching);
// }
return new IndexedV1LocalFileSystem(rootPath, isVersioned, readOnly,
enableAsyncronousDispatching, true);
}
@ -154,7 +151,7 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Returns true if any file found within dir whose name starts
* with '~' character (e.g., ~index.dat, etc)
* @param dir
* @param dir directory to inspect
* @return true if any hidden file found with '~' prefix
*/
private static boolean hasAnyHiddenFiles(File dir) {
@ -237,7 +234,7 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Associate file system with a specific repository logger
* @param repositoryLogger
* @param repositoryLogger repository logger (may be null)
*/
public void setAssociatedRepositoryLogger(RepositoryLogger repositoryLogger) {
this.repositoryLogger = repositoryLogger;
@ -317,8 +314,14 @@ public abstract class LocalFileSystem implements FileSystem {
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
@ -336,19 +339,19 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Find an existing storage location
* @param folderPath
* @param itemName
* @param folderPath folder path of item
* @param itemName item name
* @return storage location. A non-null value does not guarantee that the associated
* item actually exists.
* @throws FileNotFoundException
* @throws FileNotFoundException if existing storage allocation not found
*/
protected abstract ItemStorage findItemStorage(String folderPath, String itemName)
throws FileNotFoundException;
/**
* Allocate a new storage location
* @param folderPath
* @param itemName
* @param folderPath folder path of item
* @param itemName item name
* @return storage location
* @throws DuplicateFileException if item path has previously been allocated
* @throws IOException if invalid path/item name specified
@ -359,9 +362,9 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Deallocate item storage
* @param folderPath
* @param itemName
* @throws IOException
* @param folderPath folder path of item
* @param itemName item name
* @throws IOException if an IO error occurs
*/
protected abstract void deallocateItemStorage(String folderPath, String itemName)
throws IOException;
@ -376,15 +379,27 @@ public abstract class LocalFileSystem implements FileSystem {
@Override
public synchronized LocalFolderItem getItem(String folderPath, String name) throws IOException {
ItemStorage itemStorage = null;
try {
ItemStorage itemStorage = findItemStorage(folderPath, name);
itemStorage = findItemStorage(folderPath, name);
if (itemStorage == null) {
return null;
}
PropertyFile propertyFile = itemStorage.getPropertyFile();
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
if (propertyFile.exists()) {
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) {
// ignore
@ -394,11 +409,12 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Notification that FileID has been changed within propertyFile
* @param propertyFile
* @param oldFileId
* @throws IOException
* @param propertyFile item property file
* @param oldFileId old FileId
* @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
}
@ -418,6 +434,12 @@ public abstract class LocalFileSystem implements FileSystem {
return folderItems;
}
@Override
public boolean isSupportedItemType(FolderItem folderItem) {
return (folderItem instanceof DatabaseItem) || (folderItem instanceof TextDataItem) ||
(folderItem instanceof DataFileItem);
}
@Override
public synchronized LocalDatabaseItem createDatabase(String parentPath, String name,
String fileID, BufferFile bufferFile, String comment, String contentType,
@ -434,7 +456,7 @@ public abstract class LocalFileSystem implements FileSystem {
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
LocalDatabaseItem item = null;
try {
PropertyFile propertyFile = itemStorage.getPropertyFile();
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID,
comment, resetDatabaseId, monitor, user);
}
@ -462,7 +484,7 @@ public abstract class LocalFileSystem implements FileSystem {
ItemStorage itemStorage = allocateItemStorage(parentPath, hiddenName);
LocalDatabaseItem item = null;
try {
PropertyFile propertyFile = itemStorage.getPropertyFile();
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID, null,
resetDatabaseId, monitor, null);
}
@ -489,7 +511,7 @@ public abstract class LocalFileSystem implements FileSystem {
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
LocalManagedBufferFile bufferFile = null;
try {
PropertyFile propertyFile = itemStorage.getPropertyFile();
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
bufferFile = LocalDatabaseItem.create(this, propertyFile, bufferSize, contentType,
fileID, user, projectPath);
}
@ -502,7 +524,7 @@ public abstract class LocalFileSystem implements FileSystem {
}
@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)
throws InvalidNameException, IOException, CancelledException {
@ -514,11 +536,12 @@ public abstract class LocalFileSystem implements FileSystem {
testValidName(name, false);
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
LocalDataFile dataFile = null;
LocalDataFileItem dataFile = null;
try {
//TODO handle comment
PropertyFile propertyFile = itemStorage.getPropertyFile();
dataFile = new LocalDataFile(this, propertyFile, istream, contentType, monitor);
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
dataFile = new LocalDataFileItem(this, propertyFile, istream, contentType, monitor);
dataFile.log("file created", getUserName());
}
finally {
if (dataFile == null) {
@ -531,6 +554,38 @@ public abstract class LocalFileSystem implements FileSystem {
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
public LocalDatabaseItem createFile(String parentPath, String name, File packedFile,
TaskMonitor monitor, String user)
@ -561,7 +616,7 @@ public abstract class LocalFileSystem implements FileSystem {
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
LocalDatabaseItem item = null;
try {
PropertyFile propertyFile = itemStorage.getPropertyFile();
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
item =
new LocalDatabaseItem(this, propertyFile, packedFile, contentType, monitor, user);
}
@ -661,6 +716,7 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Returns file system listener.
* @return file system listener or null
*/
FileSystemListener getListener() {
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.
*/
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
* an item have been removed from the filesystem.
* @param folderPath
* @param itemName
* @throws IOException
* @param folderPath folder path of item
* @param itemName item name
* @throws IOException if an IO error occurs
*/
protected synchronized void itemDeleted(String folderPath, String itemName) throws IOException {
// do nothing
@ -768,6 +825,7 @@ public abstract class LocalFileSystem implements FileSystem {
* Returns the full path for a specific folder or item
* @param parentPath full parent path
* @param name child folder or item name
* @return pathname
*/
protected final static String getPath(String parentPath, String name) {
if (parentPath.length() == 1) {
@ -848,7 +906,7 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* Escape hidden prefix chars in name
* @param name
* @param name name to be escaped
* @return escaped name
*/
public static final String escapeHiddenDirPrefixChars(String name) {
@ -867,7 +925,7 @@ public abstract class LocalFileSystem implements FileSystem {
/**
* 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
*/
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 ghidra.framework.store.*;
import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
@ -49,7 +50,7 @@ public abstract class LocalFolderItem implements FolderItem {
static final String DATA_DIR_EXTENSION = ".db";
final PropertyFile propertyFile;
final ItemPropertyFile propertyFile;
final CheckoutManager checkoutMgr;
final HistoryManager historyMgr;
final LocalFileSystem fileSystem;
@ -69,7 +70,7 @@ public abstract class LocalFolderItem implements FolderItem {
* @param fileSystem file system
* @param propertyFile property file
*/
LocalFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
LocalFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile) {
this.fileSystem = fileSystem;
this.propertyFile = propertyFile;
this.isVersioned = fileSystem.isVersioned();
@ -90,7 +91,7 @@ public abstract class LocalFolderItem implements FolderItem {
* @param create if true the data directory will be created
* @throws IOException
*/
LocalFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile, boolean useDataDir,
LocalFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, boolean useDataDir,
boolean create) throws IOException {
this.fileSystem = fileSystem;
this.propertyFile = propertyFile;
@ -121,7 +122,7 @@ public abstract class LocalFolderItem implements FolderItem {
throw new FileNotFoundException(getName() + " not found");
}
if (isVersioned) {
if (isVersioned && useDataDir) {
checkoutMgr = new CheckoutManager(this, create);
historyMgr = new HistoryManager(this, create);
}
@ -161,7 +162,7 @@ public abstract class LocalFolderItem implements FolderItem {
final File getDataDir() {
synchronized (fileSystem) {
// Use hidden DB directory
return new File(propertyFile.getFolder(),
return new File(propertyFile.getParentStorageDirectory(),
LocalFileSystem.HIDDEN_DIR_PREFIX +
LocalFileSystem.escapeHiddenDirPrefixChars(propertyFile.getStorageName()) +
DATA_DIR_EXTENSION);
@ -234,6 +235,9 @@ public abstract class LocalFolderItem implements FolderItem {
*/
void beginCheckin(long checkoutId) throws FileInUseException {
synchronized (fileSystem) {
if (checkoutMgr == null) {
throw new UnsupportedOperationException("item does not support checkin/checkout");
}
if (checkinId != DEFAULT_CHECKOUT_ID) {
ItemCheckoutStatus status;
try {
@ -426,7 +430,7 @@ public abstract class LocalFolderItem implements FolderItem {
synchronized (fileSystem) {
checkInUse();
File oldFolder = propertyFile.getFolder();
File oldFolder = propertyFile.getParentStorageDirectory();
String oldStorageName = propertyFile.getStorageName();
String oldPath = propertyFile.getParentPath();
File oldDbDir = getDataDir();
@ -491,41 +495,6 @@ public abstract class LocalFolderItem implements FolderItem {
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()
*/
@ -590,6 +559,10 @@ public abstract class LocalFolderItem implements FolderItem {
throw new UnsupportedOperationException(
"Non-versioned item does not support getVersions");
}
if (historyMgr == null) {
throw new UnsupportedOperationException(
"getVersions not supported without history manager");
}
return historyMgr.getVersions();
}
}
@ -652,12 +625,16 @@ public abstract class LocalFolderItem implements FolderItem {
@Override
public ItemCheckoutStatus checkout(CheckoutType checkoutType, String user, String projectPath)
throws IOException {
if (checkoutMgr == null) {
throw new UnsupportedOperationException("item does not support checkin/checkout");
}
if (!isVersioned) {
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
}
if (fileSystem.isReadOnly()) {
throw new ReadOnlyException();
}
synchronized (fileSystem) {
ItemCheckoutStatus coStatus =
@ -672,6 +649,9 @@ public abstract class LocalFolderItem implements FolderItem {
@Override
public void terminateCheckout(long checkoutId, boolean notify) throws IOException {
if (checkoutMgr == null) {
throw new UnsupportedOperationException("item does not support checkin/checkout");
}
if (!isVersioned) {
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
}
@ -700,6 +680,9 @@ public abstract class LocalFolderItem implements FolderItem {
throw new UnsupportedOperationException(
"Non-versioned item does not support checkout");
}
if (checkoutMgr == null) {
return null;
}
return checkoutMgr.getCheckout(checkoutId);
}
}
@ -711,6 +694,9 @@ public abstract class LocalFolderItem implements FolderItem {
throw new UnsupportedOperationException(
"Non-versioned item does not support checkout");
}
if (checkoutMgr == null) {
return new ItemCheckoutStatus[0];
}
return checkoutMgr.getAllCheckouts();
}
}
@ -802,33 +788,39 @@ public abstract class LocalFolderItem implements FolderItem {
* @param propertyFile property file which identifies the 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);
try {
if (fileType == DATAFILE_FILE_TYPE) {
return new LocalDataFile(fileSystem, propertyFile);
return new LocalDataFileItem(fileSystem, propertyFile);
}
else if (fileType == DATABASE_FILE_TYPE) {
return new LocalDatabaseItem(fileSystem, propertyFile);
}
else if (fileType == LINK_FILE_TYPE) {
return new LocalTextDataItem(fileSystem, propertyFile);
}
else if (fileType == UNKNOWN_FILE_TYPE) {
log.error("Folder item has unspecified file type: " +
new File(propertyFile.getFolder(), propertyFile.getStorageName()));
log.error("Folder item has unspecified file type: " + new File(
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
}
else {
log.error("Folder item has unsupported file type (" + fileType + "): " +
new File(propertyFile.getFolder(), propertyFile.getStorageName()));
log.error("Folder item has unsupported file type (" + fileType + "): " + new File(
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
}
}
catch (FileNotFoundException e) {
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) {
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
@ -836,7 +828,7 @@ public abstract class LocalFolderItem implements FolderItem {
synchronized (fileSystem) {
if (isVersioned) {
try {
return checkoutMgr.isCheckedOut();
return checkoutMgr != null && checkoutMgr.isCheckedOut();
}
catch (IOException e) {
Msg.error(getName() + " versioning error", e);
@ -865,6 +857,11 @@ public abstract class LocalFolderItem implements FolderItem {
return false;
}
@Override
public int hashCode() {
return propertyFile.hashCode();
}
/**
* Update this non-versioned item with the latest version of the specified versioned item.
* @param versionedFolderItem versioned item which corresponds to this
@ -892,6 +889,9 @@ public abstract class LocalFolderItem implements FolderItem {
@Override
public void updateCheckoutVersion(long checkoutId, int checkoutVersion, String user)
throws IOException {
if (checkoutMgr == null) {
throw new UnsupportedOperationException("item does not support checkin/checkout");
}
if (!isVersioned) {
throw new UnsupportedOperationException(
"updateCheckoutVersion is not applicable to non-versioned item");
@ -907,4 +907,5 @@ public abstract class LocalFolderItem implements FolderItem {
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 ghidra.framework.store.*;
import ghidra.util.PropertyFile;
import ghidra.util.task.TaskMonitor;
/**
* <code>UnknownFolderItem</code> acts as a LocalFolderItem place-holder for
* items of an unknown type.
*/
public class UnknownFolderItem extends LocalFolderItem {
public static final String UNKNOWN_CONTENT_TYPE = "Unknown-File";
public class LocalUnknownFolderItem extends LocalFolderItem implements UnknownFolderItem {
private final int fileType;
@ -37,11 +34,11 @@ public class UnknownFolderItem extends LocalFolderItem {
* @param fileSystem local file system
* @param propertyFile property file associated with this item
*/
UnknownFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
LocalUnknownFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile) {
super(fileSystem, propertyFile);
fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
}
/**
* Get the file type
* @return file type or -1 if unspecified
@ -55,134 +52,82 @@ public class UnknownFolderItem extends LocalFolderItem {
return 0;
}
/*
* @see ghidra.framework.store.FolderItem#updateCheckout(ghidra.framework.store.FolderItem, boolean, ghidra.util.task.TaskMonitor)
*/
@Override
public void updateCheckout(FolderItem versionedFolderItem, boolean updateItem,
TaskMonitor monitor) throws IOException {
throw new UnsupportedOperationException();
}
/*
* @see ghidra.framework.store.FolderItem#updateCheckout(ghidra.framework.store.FolderItem, int)
*/
@Override
public void updateCheckout(FolderItem item, int checkoutVersion) throws IOException {
throw new UnsupportedOperationException();
}
/*
* @see ghidra.framework.store.FolderItem#checkout(java.lang.String)
*/
public synchronized ItemCheckoutStatus checkout(String user) throws IOException {
throw new IOException(propertyFile.getName() +
" may not be checked-out, item may be corrupt");
throw new IOException(
propertyFile.getName() + " may not be checked-out, item may be corrupt");
}
/*
* @see ghidra.framework.store.FolderItem#terminateCheckout(long)
*/
public synchronized void terminateCheckout(long checkoutId) {
// Do nothing
}
/*
* @see ghidra.framework.store.FolderItem#clearCheckout()
*/
@Override
public void clearCheckout() throws IOException {
// Do nothing
}
/*
* @see ghidra.framework.store.FolderItem#setCheckout(long, int, int)
*/
public void setCheckout(long checkoutId, int checkoutVersion, int localVersion) {
// Do nothing
}
/*
* @see ghidra.framework.store.FolderItem#getCheckout(long)
*/
@Override
public synchronized ItemCheckoutStatus getCheckout(long checkoutId) throws IOException {
return null;
}
/*
* @see ghidra.framework.store.FolderItem#getCheckouts()
*/
@Override
public synchronized ItemCheckoutStatus[] getCheckouts() throws IOException {
return new ItemCheckoutStatus[0];
}
/*
* @see ghidra.framework.store.FolderItem#getVersions()
*/
@Override
public synchronized Version[] getVersions() throws IOException {
throw new IOException("History data is unavailable for " + propertyFile.getName());
}
/*
* @see ghidra.framework.store.FolderItem#getContentType()
*/
@Override
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;
}
/*
* @see ghidra.framework.store.local.LocalFolderItem#deleteMinimumVersion(java.lang.String)
*/
@Override
void deleteMinimumVersion(String user) throws IOException {
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
}
/*
* @see ghidra.framework.store.local.LocalFolderItem#deleteCurrentVersion(java.lang.String)
*/
@Override
void deleteCurrentVersion(String user) throws IOException {
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
}
/*
* @see ghidra.framework.store.FolderItem#output(java.io.File, int, ghidra.util.task.TaskMonitor)
*/
@Override
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
throw new UnsupportedOperationException("Output not supported for UnknownFolderItems");
}
/*
* @see ghidra.framework.store.local.LocalFolderItem#getMinimumVersion()
*/
@Override
int getMinimumVersion() throws IOException {
return -1;
}
/*
* @see ghidra.framework.store.FolderItem#getCurrentVersion()
*/
@Override
public int getCurrentVersion() {
return -1;
}
/*
* @see ghidra.framework.store.FolderItem#canRecover()
*/
@Override
public boolean canRecover() {
return false;

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