Merge remote-tracking branch 'origin/GP-5825_dev747368_refactor_localfilesystemsub_out'

This commit is contained in:
Ryan Kurtz 2025-07-17 13:50:47 -04:00
commit 12a8db6195
12 changed files with 173 additions and 532 deletions

View file

@ -15,9 +15,7 @@
*/ */
package ghidra.app.util.opinion; package ghidra.app.util.opinion;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -712,7 +710,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
Program libraryProgram = null; Program libraryProgram = null;
String simpleLibraryName = FilenameUtils.getName(library); String simpleLibraryName = FilenameUtils.getName(library);
boolean isAbsolute = new File(library).isAbsolute(); boolean isAbsolute = isAbsoluteLibraryPath(library);
boolean success = false; boolean success = false;
try { try {
@ -847,13 +845,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
try { try {
for (LibrarySearchPath searchPath : searchPaths) { for (LibrarySearchPath searchPath : searchPaths) {
monitor.checkCancelled(); monitor.checkCancelled();
String fullLibraryPath = joinPaths(searchPath.relativeFsPath(), library); String fullLibraryPath =
FSUtilities.appendPath(searchPath.relativeFsPath(), library);
GFileSystem fs = searchPath.fsRef().getFilesystem(); GFileSystem fs = searchPath.fsRef().getFilesystem();
FSRL fsrl = resolveLibraryFile(fs, fullLibraryPath); FSRL fsrl = resolveLibraryFile(fs, fullLibraryPath);
Optional.ofNullable(fsrl).ifPresent(results::add); Optional.ofNullable(fsrl).ifPresent(results::add);
} }
if (results.isEmpty() && new File(library).isAbsolute()) { if (results.isEmpty() && isAbsoluteLibraryPath(library)) {
LocalFileSystem localFS = FileSystemService.getInstance().getLocalFS(); LocalFileSystem localFS = FileSystemService.getInstance().getLocalFS();
FSRL fsrl = resolveLibraryFile(localFS, library); FSRL fsrl = resolveLibraryFile(localFS, library);
Optional.ofNullable(fsrl).ifPresent(results::add); Optional.ofNullable(fsrl).ifPresent(results::add);
@ -1054,7 +1053,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* A library search path * A library search path
* *
* @param fsRef The root {@link FileSystemRef} * @param fsRef The root {@link FileSystemRef}
* @param relativeFsPath A {@link Path} relative to the root of the file system, or null for the * @param relativeFsPath string path, relative to the root of the file system, or null for the
* root * root
*/ */
protected record LibrarySearchPath(FileSystemRef fsRef, String relativeFsPath) {} protected record LibrarySearchPath(FileSystemRef fsRef, String relativeFsPath) {}
@ -1122,30 +1121,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
continue; continue;
} }
if (fsService.isLocal(fsrl)) {
try {
FileSystemRef fileRef =
fsService.probeFileForFilesystem(fsrl, monitor, null);
if (fileRef != null) {
result.add(new LibrarySearchPath(fileRef, null));
}
}
catch (IOException e) {
log.appendMsg(e.getMessage());
}
}
else {
try (RefdFile fileRef = fsService.getRefdFile(fsrl, monitor)) { try (RefdFile fileRef = fsService.getRefdFile(fsrl, monitor)) {
if (fileRef != null) { if (fileRef != null) {
File f = new File(fileRef.file.getPath()); // File API will sanitize Windows-style paths result.add(
result.add(new LibrarySearchPath(fileRef.fsRef.dup(), f.getPath())); new LibrarySearchPath(fileRef.fsRef.dup(), fileRef.file.getPath()));
} }
} }
catch (IOException e) { catch (IOException e) {
log.appendMsg(e.getMessage()); log.appendMsg(e.getMessage());
} }
} }
}
success = true; success = true;
} }
finally { finally {
@ -1242,4 +1227,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
? String.CASE_INSENSITIVE_ORDER ? String.CASE_INSENSITIVE_ORDER
: (s1, s2) -> s1.compareTo(s2); : (s1, s2) -> s1.compareTo(s2);
} }
/**
* Performs a platform-independent test to see if the given path is absolute
*
* @param path The path to test
* @return True if the given path is absolute; otherwise, false
*/
private boolean isAbsoluteLibraryPath(String path) {
return FilenameUtils.getPrefixLength(path) > 0;
}
} }

View file

@ -709,13 +709,6 @@ public class FileSystemService {
if (ref != null) { if (ref != null) {
return ref; return ref;
} }
GFileSystem subdirFS = probeForLocalSubDirFilesystem(containerFSRL);
if (subdirFS != null) {
ref = subdirFS.getRefManager().create();
fsInstanceManager.add(subdirFS);
return ref;
}
} }
// Normal case, probe the container file and create a filesystem instance. // Normal case, probe the container file and create a filesystem instance.
@ -748,18 +741,6 @@ public class FileSystemService {
return null; return null;
} }
private GFileSystem probeForLocalSubDirFilesystem(FSRL containerFSRL) {
if (localFS.isLocalSubdir(containerFSRL)) {
try {
return localFS.getSubFileSystem(containerFSRL);
}
catch (IOException e) {
Msg.error(this, "Problem when probing for local directory: ", e);
}
}
return null;
}
/** /**
* Mount a specific file system (by class) using a specified container file. * Mount a specific file system (by class) using a specified container file.
* <p> * <p>
@ -815,11 +796,6 @@ public class FileSystemService {
public GFileSystem openFileSystemContainer(FSRL containerFSRL, TaskMonitor monitor) public GFileSystem openFileSystemContainer(FSRL containerFSRL, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, IOException {
GFileSystem subdirFS = probeForLocalSubDirFilesystem(containerFSRL);
if (subdirFS != null) {
return subdirFS;
}
ByteProvider byteProvider = getByteProvider(containerFSRL, true, monitor); ByteProvider byteProvider = getByteProvider(containerFSRL, true, monitor);
return fsFactoryMgr.probe(byteProvider, this, null, FileSystemInfo.PRIORITY_LOWEST, return fsFactoryMgr.probe(byteProvider, this, null, FileSystemInfo.PRIORITY_LOWEST,
monitor); monitor);

View file

@ -1,117 +0,0 @@
/* ###
* 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.formats.gfilesystem;
import java.io.File;
import java.util.Objects;
/**
* {@link GFile} implementation that refers to a real java.io.File on the local
* file system.
* <p>
* This implementation keeps track of the FSRL and GFile path separately so that
* they can be different, as is the case with LocalFileSystemSub files that
* have real FSRLs but fake relative paths.
*/
public class GFileLocal implements GFile {
private GFileSystem fs;
private FSRL fsrl;
private String path;
private File f;
private GFile parent;
/**
* Create new GFileLocal instance.
*
* @param f {@link File} on the local filesystem
* @param path String path (including filename) of this instance
* @param fsrl {@link FSRL} of this instance
* @param fs {@link GFileSystem} that created this file.
* @param parent Parent directory that contains this file, or null if parent is root.
*/
public GFileLocal(File f, String path, FSRL fsrl, GFileSystem fs, GFile parent) {
this.fs = fs;
this.fsrl = fsrl;
this.path = path;
this.f = f;
this.parent = parent;
}
@Override
public GFileSystem getFilesystem() {
return fs;
}
@Override
public FSRL getFSRL() {
return fsrl;
}
@Override
public GFile getParentFile() {
return parent;
}
@Override
public String getPath() {
return path;
}
@Override
public String getName() {
return fsrl.getName();
}
@Override
public boolean isDirectory() {
return f.isDirectory();
}
@Override
public long getLength() {
return f.length();
}
public File getLocalFile() {
return f;
}
@Override
public String toString() {
return "Local " + f.toString() + " with path " + path;
}
@Override
public int hashCode() {
return Objects.hash(f, fs, fsrl, parent, path);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GFileLocal)) {
return false;
}
GFileLocal other = (GFileLocal) obj;
return Objects.equals(f, other.f) && Objects.equals(fs, other.fs) &&
Objects.equals(fsrl, other.fsrl) && Objects.equals(parent, other.parent) &&
Objects.equals(path, other.path);
}
}

View file

@ -74,21 +74,6 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
return fsFSRL.equals(fsrl.getFS()); return fsFSRL.equals(fsrl.getFS());
} }
/**
* Creates a new file system instance that is a sub-view limited to the specified directory.
*
* @param fsrl {@link FSRL} that must be a directory in this local filesystem
* @return new {@link LocalFileSystemSub} instance
* @throws IOException if bad FSRL
*/
public LocalFileSystemSub getSubFileSystem(FSRL fsrl) throws IOException {
if (isLocalSubdir(fsrl)) {
File localDir = getLocalFile(fsrl);
return new LocalFileSystemSub(localDir, this);
}
return null;
}
/** /**
* Returns true if the {@link FSRL} is a local filesystem subdirectory. * Returns true if the {@link FSRL} is a local filesystem subdirectory.
* *
@ -136,7 +121,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
return fsFSRL.withPath(fsrlPath); return fsFSRL.withPath(fsrlPath);
} }
private GFile getGFile(File f) { public GFile getGFile(File f) {
List<File> parts = LocalFileSystem.getFilePathParts(f); // [/subdir/subroot/file, /subdir/subroot, /subdir, /] List<File> parts = LocalFileSystem.getFilePathParts(f); // [/subdir/subroot/file, /subdir/subroot, /subdir, /]
GFile current = rootDir; GFile current = rootDir;
for (int i = parts.size() - 2; i >= 0; i--) { for (int i = parts.size() - 2; i >= 0; i--) {

View file

@ -1,232 +0,0 @@
/* ###
* 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.formats.gfilesystem;
import java.io.*;
import java.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A {@link GFileSystem} interface to a part of the user's local / native file system.
* <p>
* This class is a sub-view of the {@link LocalFileSystem}, and returns hybrid GFile objects
* that have fully specified FSRL paths that are valid in the Root filesystem, but relative
* GFile paths.
* <p>
* This class's name doesn't end with "FileSystem" to ensure it will not be auto-discovered
* by the FileSystemFactoryMgr.
*
*/
public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
private final FSRLRoot fsFSRL;
private final LocalFileSystem rootFS;
private File localfsRootDir;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private GFileLocal rootGFile;
public LocalFileSystemSub(File rootDir, LocalFileSystem rootFS) throws IOException {
this.rootFS = rootFS;
this.localfsRootDir = rootDir.getCanonicalFile();
FSRL containerFSRL = rootFS.getLocalFSRL(localfsRootDir);
this.fsFSRL = FSRLRoot.nestedFS(containerFSRL, rootFS.getFSRL().getProtocol());
this.rootGFile = new GFileLocal(localfsRootDir, "/", containerFSRL, this, null);
}
@Override
public String getType() {
return rootFS.getType();
}
@Override
public String getDescription() {
return "Local filesystem subdirectory";
}
@Override
public void close() {
refManager.onClose();
localfsRootDir = null;
}
@Override
public boolean isClosed() {
return localfsRootDir == null;
}
@Override
public boolean isStatic() {
return false;
}
private File getFileFromGFile(GFile gf) throws IOException {
if (gf == null) {
return localfsRootDir;
}
if (!(gf instanceof GFileLocal)) {
throw new IOException("Unexpected GFile class: " + gf.getClass());
}
return ((GFileLocal) gf).getLocalFile();
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
if (directory == null) {
directory = rootGFile;
}
if (!directory.isDirectory()) {
return List.of();
}
File localDir = getFileFromGFile(directory);
if (FSUtilities.isSymlink(localDir)) {
return List.of();
}
File[] localFiles = localDir.listFiles();
if (localFiles == null) {
return List.of();
}
List<GFile> tmp = new ArrayList<>(localFiles.length);
FSRL dirFSRL = directory.getFSRL();
String relPath = directory.getPath(); // this is the clean relative path assigned to the dir GFile earlier
for (File f : localFiles) {
boolean isSymlink = FSUtilities.isSymlink(f); // check this manually to allow broken symlinks to appear in listing
if (!(isSymlink || f.isFile() || f.isDirectory())) {
// skip non-file things
continue;
}
// construct a GFile with split personality... a relative GFile pathname but
// an absolute FSRL path
String name = f.getName();
GFileLocal gf = new GFileLocal(f, FSUtilities.appendPath(relPath, name),
dirFSRL.appendPath(name), this, directory);
tmp.add(gf);
}
return tmp;
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
try {
File localFile = getFileFromGFile(file);
return rootFS.getFileAttributes(localFile);
}
catch (IOException e) {
// fail and return empty
}
return FileAttributes.EMPTY;
}
@Override
public String getName() {
return "Subdir " + localfsRootDir.getPath();
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public GFile getRootDir() {
return rootGFile;
}
@Override
public GFile lookup(String path) throws IOException {
return lookup(path, null);
}
@Override
public GFile lookup(String path, Comparator<String> nameComp) throws IOException {
File f = LocalFileSystem.lookupFile(localfsRootDir, path, nameComp);
if (f == null) {
return null;
}
GFile result = getGFile(f);
return result;
}
private GFile getGFile(File f) throws IOException {
List<File> parts = LocalFileSystem.getFilePathParts(f); // [/subdir/subroot/file, /subdir/subroot, /subdir, /]
int rootDirIndex = findRootDirIndex(parts);
if (rootDirIndex < 0) {
throw new IOException("Invalid directory " + f);
}
GFile current = rootGFile;
for (int i = rootDirIndex - 1; i >= 0; i--) {
File part = parts.get(i);
FSRL childFSRL = current.getFSRL().appendPath(part.getName());
String childPath = FSUtilities.appendPath(current.getPath(), part.getName());
current = new GFileLocal(part, childPath, childFSRL, this, current);
}
return current;
}
private int findRootDirIndex(List<File> dirList) {
for (int i = 0; i < dirList.size(); i++) {
if (localfsRootDir.equals(dirList.get(i))) {
return i;
}
}
return -1;
}
@Override
public InputStream getInputStream(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
return rootFS.getInputStream(file.getFSRL(), monitor);
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
@Override
public String toString() {
return getName();
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
return rootFS.getByteProvider(file.getFSRL(), monitor);
}
@Override
public String getMD5Hash(GFile file, boolean required, TaskMonitor monitor)
throws CancelledException, IOException {
return rootFS.getMD5Hash(file.getFSRL(), required, monitor);
}
@Override
public GFile resolveSymlinks(GFile file) throws IOException {
File f = getFileFromGFile(file);
File canonicalFile = f.getCanonicalFile();
if (f.equals(canonicalFile)) {
return file;
}
return getGFile(canonicalFile);
}
}

View file

@ -19,6 +19,7 @@ import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.awt.Component; import java.awt.Component;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -42,6 +43,7 @@ import ghidra.app.services.ProgramManager;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.fileinfo.FileAttributes; import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.plugin.importer.ImporterUtilities; import ghidra.plugin.importer.ImporterUtilities;
@ -81,20 +83,20 @@ public class FSBComponentProvider extends ComponentProviderAdapter
* ownership of the passed-in {@link FileSystemRef fsRef}. * ownership of the passed-in {@link FileSystemRef fsRef}.
* *
* @param plugin parent plugin * @param plugin parent plugin
* @param fsRef {@link FileSystemRef} to a {@link GFileSystem}. * @param rootDir {@link FileSystemRef} to a {@link GFileSystem} and a {@link GFile} root
*/ */
public FSBComponentProvider(FileSystemBrowserPlugin plugin, FileSystemRef fsRef) { public FSBComponentProvider(FileSystemBrowserPlugin plugin, RefdFile rootDir) {
super(plugin.getTool(), getDescriptiveFSName(fsRef.getFilesystem()), plugin.getName()); super(plugin.getTool(), getDescriptiveFSName(rootDir), plugin.getName());
this.plugin = plugin; this.plugin = plugin;
this.rootNode = new FSBRootNode(fsRef); this.rootNode = new FSBRootNode(rootDir);
this.pm = plugin.getTool().getService(ProgramManager.class); this.pm = plugin.getTool().getService(ProgramManager.class);
setTransient(); setTransient();
setIcon(getFSIcon(fsRef.getFilesystem(), true, fsbIcons)); setIcon(getFSIcon(rootDir.fsRef.getFilesystem(), true, fsbIcons));
initTree(); initTree();
fsRef.getFilesystem().getRefManager().addListener(this); rootDir.fsRef.getFilesystem().getRefManager().addListener(this);
initFileHandlers(); initFileHandlers();
setHelpLocation( setHelpLocation(
@ -150,8 +152,12 @@ public class FSBComponentProvider extends ComponentProviderAdapter
if (value instanceof FSBRootNode) { if (value instanceof FSBRootNode) {
// do nothing // do nothing
} }
else if (value instanceof FSBDirNode) { else if (value instanceof FSBDirNode dirNode) {
// do nothing special, but exclude FSBFileNode Icon currentIcon = getIcon();
Icon newIcon = dirNode.isSymlink()
? FSBIcons.buildIcon(currentIcon, List.of(FSBIcons.LINK_OVERLAY_ICON))
: currentIcon;
setIcon(newIcon);
} }
else if (value instanceof FSBFileNode fileNode) { else if (value instanceof FSBFileNode fileNode) {
renderFile(fileNode, selected); renderFile(fileNode, selected);
@ -367,6 +373,9 @@ public class FSBComponentProvider extends ComponentProviderAdapter
} }
public boolean ensureFileAccessable(FSRL fsrl, FSBNode node, TaskMonitor monitor) { public boolean ensureFileAccessable(FSRL fsrl, FSBNode node, TaskMonitor monitor) {
if (node instanceof FSBDirNode) {
return true;
}
FSBFileNode fileNode = (node instanceof FSBFileNode) ? (FSBFileNode) node : null; FSBFileNode fileNode = (node instanceof FSBFileNode) ? (FSBFileNode) node : null;
@ -399,9 +408,16 @@ public class FSBComponentProvider extends ComponentProviderAdapter
} }
public boolean openFileSystem(FSBNode node, boolean nested) { public boolean openFileSystem(FSBNode node, boolean nested) {
if (node instanceof FSBDirNode dirNode) {
plugin.createNewFileSystemBrowser(dirNode.getFSBRootNode().getFSRef().dup(),
dirNode.file, true);
return true;
}
if (!(node instanceof FSBFileNode fileNode) || fileNode.getFSRL() == null) { if (!(node instanceof FSBFileNode fileNode) || fileNode.getFSRL() == null) {
return false; return false;
} }
FSRL fsrl = fileNode.getFSRL(); FSRL fsrl = fileNode.getFSRL();
gTree.runTask(monitor -> { gTree.runTask(monitor -> {
if (!ensureFileAccessable(fsrl, fileNode, monitor)) { if (!ensureFileAccessable(fsrl, fileNode, monitor)) {
@ -437,7 +453,8 @@ public class FSBComponentProvider extends ComponentProviderAdapter
return; return;
} }
FSBRootNode nestedRootNode = new FSBRootNode(ref, modelFileNode); RefdFile refdRootDir = new RefdFile(ref, ref.getFilesystem().getRootDir());
FSBRootNode nestedRootNode = new FSBRootNode(refdRootDir, modelFileNode);
int indexInParent = modelFileNode.getIndexInParent(); int indexInParent = modelFileNode.getIndexInParent();
GTreeNode parent = modelFileNode.getParent(); GTreeNode parent = modelFileNode.getParent();
@ -453,7 +470,7 @@ public class FSBComponentProvider extends ComponentProviderAdapter
contextChanged(); contextChanged();
} }
else { else {
plugin.createNewFileSystemBrowser(ref, true); plugin.createNewFileSystemBrowser(ref, null, true);
} }
}); });
return true; return true;
@ -577,6 +594,18 @@ public class FSBComponentProvider extends ComponentProviderAdapter
if (destNode != null) { if (destNode != null) {
Swing.runLater(() -> gTree.setSelectedNodes(destNode)); Swing.runLater(() -> gTree.setSelectedNodes(destNode));
} }
else {
String msg = "Failed to go to %s (%s)".formatted(fileNode.symlinkDest,
destFile.getPath());
// front end tool doesn't show message when using setStatusInfo, but
// does display Msg.warn messages
if (tool instanceof FrontEndTool) {
Msg.warn(this, msg);
}
else {
tool.setStatusInfo(msg);
}
}
}); });
return; return;
} }
@ -592,15 +621,23 @@ public class FSBComponentProvider extends ComponentProviderAdapter
} }
static String getDescriptiveFSName(GFileSystem fs) { static String getDescriptiveFSName(RefdFile rootFile) {
return fs instanceof LocalFileSystem ? "My Computer" : fs.getName(); GFileSystem fs = rootFile.fsRef.getFilesystem();
GFile file = rootFile.file;
boolean isRootDir = file.getParentFile() == null;
if (fs instanceof LocalFileSystem) {
// use [new File(fsrl.path).getPath()] to transform fsrl formatted path back into
// current jvm OS specific string format
return isRootDir ? "My Computer" : new File(file.getPath()).getPath();
}
return fs.getName() + (!isRootDir ? " - " + file.getPath() : "");
} }
static Icon getFSIcon(GFileSystem fs, boolean isRootNode, FSBIcons fsbIcons) { static Icon getFSIcon(GFileSystem fs, boolean isRootNode, FSBIcons fsbIcons) {
List<Icon> overlays = !isRootNode ? List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON) : List.of(); List<Icon> overlays = !isRootNode ? List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON) : List.of();
FSRL container = fs.getFSRL().getContainer(); FSRL container = fs.getFSRL().getContainer();
String containerName = container != null ? container.getName() : "/"; String containerName = container != null ? container.getName() : "/";
Icon image = fs instanceof LocalFileSystem || fs instanceof LocalFileSystemSub Icon image = fs instanceof LocalFileSystem
? FSBIcons.MY_COMPUTER ? FSBIcons.MY_COMPUTER
: fsbIcons.getIcon(containerName, overlays); : fsbIcons.getIcon(containerName, overlays);
if (image == FSBIcons.DEFAULT_ICON) { if (image == FSBIcons.DEFAULT_ICON) {

View file

@ -141,7 +141,7 @@ public class FSBIcons {
return buildIcon(DEFAULT_ICON, overlays); return buildIcon(DEFAULT_ICON, overlays);
} }
private Icon buildIcon(Icon base, List<Icon> overlays) { public static Icon buildIcon(Icon base, List<Icon> overlays) {
if (overlays == null || overlays.isEmpty()) { if (overlays == null || overlays.isEmpty()) {
return base; return base;
} }

View file

@ -38,30 +38,30 @@ import ghidra.util.task.TaskMonitor;
*/ */
public class FSBRootNode extends FSBNode { public class FSBRootNode extends FSBNode {
private FileSystemRef fsRef; private RefdFile rootDir; // do not use RefdFile.close(), the FsRef will be extracted and closed manually
private FSBFileNode prevNode; private FSBFileNode prevNode;
private FSBRootNode modelNode; private FSBRootNode modelNode;
private boolean cryptoStatusUpdated; private boolean cryptoStatusUpdated;
private Icon icon; private Icon icon;
FSBRootNode(FileSystemRef fsRef) { FSBRootNode(RefdFile rootDir) {
this(fsRef, null); this(rootDir, null);
} }
FSBRootNode(FileSystemRef fsRef, FSBFileNode prevNode) { FSBRootNode(RefdFile rootDir, FSBFileNode prevNode) {
super(FSBComponentProvider.getDescriptiveFSName(fsRef.getFilesystem())); super(FSBComponentProvider.getDescriptiveFSName(rootDir));
this.fsRef = fsRef; this.rootDir = rootDir;
this.prevNode = prevNode; this.prevNode = prevNode;
this.modelNode = this; this.modelNode = this;
this.icon = FSBComponentProvider.getFSIcon(fsRef.getFilesystem(), prevNode == null, this.icon = FSBComponentProvider.getFSIcon(rootDir.fsRef.getFilesystem(), prevNode == null,
FSBIcons.getInstance()); FSBIcons.getInstance());
} }
@Override @Override
public GTreeNode clone() throws CloneNotSupportedException { public GTreeNode clone() throws CloneNotSupportedException {
FSBRootNode clone = (FSBRootNode) super.clone(); FSBRootNode clone = (FSBRootNode) super.clone();
clone.fsRef = null; // stomp on the clone's fsRef to force it to use modelNode's fsRef clone.rootDir = null; // stomp on the clone's fsRef to force it to use modelNode's fsRef
return clone; return clone;
} }
@ -103,19 +103,19 @@ public class FSBRootNode extends FSBNode {
@Override @Override
public GFile getGFile() { public GFile getGFile() {
return fsRef.getFilesystem().getRootDir(); return rootDir.file;
} }
public FileSystemRef getFSRef() { public FileSystemRef getFSRef() {
return modelNode.fsRef; return modelNode.rootDir.fsRef;
} }
private void releaseFSRefIfModelNode() { private void releaseFSRefIfModelNode() {
if (this != modelNode) { if (this != modelNode) {
return; return;
} }
FileSystemService.getInstance().releaseFileSystemImmediate(fsRef); FileSystemService.getInstance().releaseFileSystemImmediate(rootDir.fsRef);
fsRef = null; rootDir = null;
} }
@Override @Override
@ -142,10 +142,9 @@ public class FSBRootNode extends FSBNode {
@Override @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException { public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
if (fsRef != null) { if (rootDir != null) {
try { try {
return FSBNode.createNodesFromFileList(fsRef.getFilesystem().getListing(null), return FSBNode.createNodesFromFileList(rootDir.file.getListing(), monitor);
monitor);
} }
catch (IOException e) { catch (IOException e) {
FSUtilities.displayException(this, null, "Error Opening File System", FSUtilities.displayException(this, null, "Error Opening File System",
@ -157,15 +156,18 @@ public class FSBRootNode extends FSBNode {
@Override @Override
public FSRL getFSRL() { public FSRL getFSRL() {
return modelNode != null && modelNode.fsRef != null return modelNode != null && modelNode.rootDir != null
? modelNode.fsRef.getFilesystem().getFSRL() ? modelNode.rootDir.file.getFSRL()
: null; : null;
} }
public FSBNode getGFileFSBNode(GFile file, TaskMonitor monitor) { public FSBNode getGFileFSBNode(GFile file, TaskMonitor monitor) {
List<GFile> pathParts = splitGFilePath(file); List<GFile> pathParts = splitGFilePath(file);
List<GFile> rootPathParts = splitGFilePath(rootDir.file);
// TODO: ensure pathParts has a prefix that equals rootPathParts
FSBNode fileNode = this; FSBNode fileNode = this;
for (int i = 1 /* skip root */; fileNode != null && i < pathParts.size(); i++) { for (int i = rootPathParts.size() /* skip root */; fileNode != null &&
i < pathParts.size(); i++) {
try { try {
fileNode = fileNode.findMatchingNode(pathParts.get(i), monitor); fileNode = fileNode.findMatchingNode(pathParts.get(i), monitor);
} }
@ -177,10 +179,11 @@ public class FSBRootNode extends FSBNode {
} }
public FSRL getContainer() { public FSRL getContainer() {
// use the rootDir's FSRL to sidestep issue with LocalFileSystemSub's non-standard fsFSRL // allows the import of the file container of a filesystem image
return fsRef != null if ( rootDir != null && rootDir.file.getParentFile() == null ) {
? fsRef.getFilesystem().getRootDir().getFSRL().getFS().getContainer() return rootDir.fsRef.getFilesystem().getFSRL().getContainer();
: null; }
return null;
} }
private List<GFile> splitGFilePath(GFile f) { private List<GFile> splitGFilePath(GFile f) {
@ -193,8 +196,8 @@ public class FSBRootNode extends FSBNode {
} }
public FSRL getProgramProviderFSRL(FSRL fsrl) { public FSRL getProgramProviderFSRL(FSRL fsrl) {
if (fsRef != null) { if (rootDir != null) {
GFileSystem fs = fsRef.getFilesystem(); GFileSystem fs = rootDir.fsRef.getFilesystem();
if (fs instanceof GFileSystemProgramProvider programProviderFS) { if (fs instanceof GFileSystemProgramProvider programProviderFS) {
try { try {
GFile gfile = fs.lookup(fsrl.getPath()); GFile gfile = fs.lookup(fsrl.getPath());

View file

@ -140,22 +140,28 @@ public class FileSystemBrowserPlugin extends Plugin
* method). * method).
* *
* @param fsRef {@link FileSystemRef} of open {@link GFileSystem} * @param fsRef {@link FileSystemRef} of open {@link GFileSystem}
* @param rootDir directory to use as the root of the filesystem's tree, or {@code null} to
* specify the filesystem's actual rootdir
* @param show boolean true if the new browser component should be shown * @param show boolean true if the new browser component should be shown
*/ */
public void createNewFileSystemBrowser(FileSystemRef fsRef, boolean show) { public void createNewFileSystemBrowser(FileSystemRef fsRef, GFile rootDir, boolean show) {
Swing.runIfSwingOrRunLater(() -> doCreateNewFileSystemBrowser(fsRef, show)); if (rootDir == null) {
rootDir = fsRef.getFilesystem().getRootDir();
}
RefdFile refdRootDir = new RefdFile(fsRef, rootDir);
Swing.runIfSwingOrRunLater(() -> doCreateNewFileSystemBrowser(refdRootDir, show));
} }
private void doCreateNewFileSystemBrowser(FileSystemRef fsRef, boolean show) { private void doCreateNewFileSystemBrowser(RefdFile rootFile, boolean show) {
FSRLRoot fsFSRL = fsRef.getFilesystem().getFSRL(); FSRL rootFSRL = rootFile.file.getFSRL();
FSBComponentProvider provider = currentBrowsers.get(fsFSRL); FSBComponentProvider provider = currentBrowsers.get(rootFSRL);
if (provider != null) { if (provider != null) {
Msg.info(this, "Filesystem browser already open for " + fsFSRL); Msg.info(this, "Filesystem browser already open for " + rootFSRL);
fsRef.close(); FSUtilities.uncheckedClose(rootFile, null);
} }
else { else {
provider = new FSBComponentProvider(this, fsRef); provider = new FSBComponentProvider(this, rootFile);
currentBrowsers.put(fsFSRL, provider); currentBrowsers.put(rootFSRL, provider);
getTool().addComponentProvider(provider, false); getTool().addComponentProvider(provider, false);
provider.afterAddedToTool(); provider.afterAddedToTool();
provider.contextChanged(); provider.contextChanged();
@ -201,32 +207,6 @@ public class FileSystemBrowserPlugin extends Plugin
chooserOpen.setLastDirectoryPreference(LAST_FS_DIR); chooserOpen.setLastDirectoryPreference(LAST_FS_DIR);
} }
/**
* Worker function for doOpenFilesystem, meant to be called in a task thread.
*
* @param containerFSRL {@link FSRL} of the container to open
* @param parent parent {@link Component} for error dialogs, null ok
* @param monitor {@link TaskMonitor} to watch and update.
*/
private void doOpenFilesystem(FSRL containerFSRL, Component parent, TaskMonitor monitor) {
try {
monitor.setMessage("Probing " + containerFSRL.getName() + " for filesystems");
FileSystemRef ref = fsService().probeFileForFilesystem(containerFSRL, monitor,
FileSystemProbeConflictResolver.GUI_PICKER);
if (ref == null) {
Msg.showWarn(this, parent, "Open Filesystem",
"No filesystem provider for " + containerFSRL.getName());
return;
}
createNewFileSystemBrowser(ref, true);
}
catch (IOException | CancelledException e) {
FSUtilities.displayException(this, parent, "Open Filesystem Error",
"Error opening filesystem for " + containerFSRL.getName(), e);
}
}
/** /**
* Prompts the user to pick a file system container file to open using a local * Prompts the user to pick a file system container file to open using a local
* filesystem browser and then displays that filesystem in a new fsb browser. * filesystem browser and then displays that filesystem in a new fsb browser.
@ -255,12 +235,45 @@ public class FileSystemBrowserPlugin extends Plugin
return; return;
} }
FSRL containerFSRL = fsService().getLocalFSRL(file); LocalFileSystem localFS = fsService().getLocalFS();
if (file.isDirectory()) {
createNewFileSystemBrowser(localFS.getRefManager().create(), localFS.getGFile(file),
true);
return;
}
TaskLauncher.launchModal("Open File System", (monitor) -> { TaskLauncher.launchModal("Open File System", (monitor) -> {
FSRL containerFSRL = localFS.getLocalFSRL(file);
doOpenFilesystem(containerFSRL, parent, monitor); doOpenFilesystem(containerFSRL, parent, monitor);
}); });
} }
/**
* Worker function for doOpenFilesystem, meant to be called in a task thread.
*
* @param containerFSRL {@link FSRL} of the container to open
* @param parent parent {@link Component} for error dialogs, null ok
* @param monitor {@link TaskMonitor} to watch and update.
*/
private void doOpenFilesystem(FSRL containerFSRL, Component parent, TaskMonitor monitor) {
try {
monitor.setMessage("Probing " + containerFSRL.getName() + " for filesystems");
FileSystemRef ref = fsService().probeFileForFilesystem(containerFSRL, monitor,
FileSystemProbeConflictResolver.GUI_PICKER);
if (ref == null) {
Msg.showWarn(this, parent, "Open Filesystem",
"No filesystem provider for " + containerFSRL.getName());
return;
}
createNewFileSystemBrowser(ref, null, true);
}
catch (IOException | CancelledException e) {
FSUtilities.displayException(this, parent, "Open Filesystem Error",
"Error opening filesystem for " + containerFSRL.getName(), e);
}
}
private FileSystemService fsService() { private FileSystemService fsService() {
// use a delayed initialization so we don't force the FileSystemService to initialize // use a delayed initialization so we don't force the FileSystemService to initialize
if (fsService == null) { if (fsService == null) {

View file

@ -52,7 +52,9 @@ public class ListMountedFSBFileHandler implements FSBFileHandler {
FileSystemRef fsRef; FileSystemRef fsRef;
if (fsFSRL != null && if (fsFSRL != null &&
(fsRef = context.fsService().getMountedFilesystem(fsFSRL)) != null) { (fsRef = context.fsService().getMountedFilesystem(fsFSRL)) != null) {
context.fsbComponent().getPlugin().createNewFileSystemBrowser(fsRef, true); context.fsbComponent()
.getPlugin()
.createNewFileSystemBrowser(fsRef, null, true);
} }
}) })
.build()); .build());

View file

@ -19,7 +19,6 @@ import java.util.List;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import ghidra.formats.gfilesystem.FileSystemRef;
import ghidra.formats.gfilesystem.FileSystemService; import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.plugins.fsbrowser.*; import ghidra.plugins.fsbrowser.*;
@ -29,6 +28,7 @@ public class OpenFsFSBFileHandler implements FSBFileHandler {
public static final String FSB_OPEN_FILE_SYSTEM_CHOOSER = "FSB Open File System Chooser"; public static final String FSB_OPEN_FILE_SYSTEM_CHOOSER = "FSB Open File System Chooser";
public static final String FSB_OPEN_FILE_SYSTEM_IN_NEW_WINDOW = public static final String FSB_OPEN_FILE_SYSTEM_IN_NEW_WINDOW =
"FSB Open File System In New Window"; "FSB Open File System In New Window";
public static final String FSB_OPEN_DIR_IN_NEW_WINDOW = "FSB Open Directory In New Window";
public static final String FSB_OPEN_FILE_SYSTEM_NESTED = "FSB Open File System Nested"; public static final String FSB_OPEN_FILE_SYSTEM_NESTED = "FSB Open File System Nested";
private FSBFileHandlerContext context; private FSBFileHandlerContext context;
@ -59,7 +59,18 @@ public class OpenFsFSBFileHandler implements FSBFileHandler {
ac.getSelectedNode() instanceof FSBFileNode fileNode && fileNode.isLeaf() && ac.getSelectedNode() instanceof FSBFileNode fileNode && fileNode.isLeaf() &&
!fileNode.isSymlink()) !fileNode.isSymlink())
.popupMenuIcon(FSBIcons.OPEN_FILE_SYSTEM) .popupMenuIcon(FSBIcons.OPEN_FILE_SYSTEM)
.popupMenuPath("Open File System in new window") .popupMenuPath("Open File System [new window]")
.popupMenuGroup("C")
.onAction(
ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), false))
.build(),
new ActionBuilder(FSB_OPEN_DIR_IN_NEW_WINDOW, context.plugin().getName())
.withContext(FSBActionContext.class)
.enabledWhen(ac -> ac.notBusy() &&
ac.getSelectedNode() instanceof FSBDirNode dirNode && !dirNode.isSymlink())
.popupMenuIcon(FSBIcons.OPEN_FILE_SYSTEM)
.popupMenuPath("Open Directory [new window]")
.popupMenuGroup("C") .popupMenuGroup("C")
.onAction( .onAction(
ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), false)) ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), false))
@ -72,9 +83,9 @@ public class OpenFsFSBFileHandler implements FSBFileHandler {
.toolBarGroup("B") .toolBarGroup("B")
.onAction(ac -> { .onAction(ac -> {
FileSystemService fsService = context.fsService(); FileSystemService fsService = context.fsService();
FileSystemRef fsRef = context.plugin()
fsService.getMountedFilesystem(fsService.getLocalFS().getFSRL()); .createNewFileSystemBrowser(
context.plugin().createNewFileSystemBrowser(fsRef, true); fsService.getLocalFS().getRefManager().create(), null, true);
}) })
.build(), .build(),
@ -84,8 +95,7 @@ public class OpenFsFSBFileHandler implements FSBFileHandler {
.toolBarIcon(FSBIcons.OPEN_FILE_SYSTEM) .toolBarIcon(FSBIcons.OPEN_FILE_SYSTEM)
.toolBarGroup("B") .toolBarGroup("B")
.onAction(ac -> context.plugin().openFileSystem()) .onAction(ac -> context.plugin().openFileSystem())
.build() .build());
);
} }
} }

View file

@ -57,35 +57,4 @@ public class LocalGFileSystemTest {
} }
@Test
public void testSubFSLookup() throws IOException {
File subworkdir = new File(workDir, "sub/Sub2/SUB3");
subworkdir.mkdirs();
File f = File.createTempFile("testfile", null, subworkdir);
try (LocalFileSystemSub subFS =
new LocalFileSystemSub(workDir, localFS)) {
GFile gfile = subFS.lookup("/sub/Sub2/SUB3/" + f.getName());
assertNotNull(gfile);
assertEquals(FSUtilities.normalizeNativePath(f.getPath()), gfile.getFSRL().getPath());
assertEquals("/sub/Sub2/SUB3/" + f.getName(), gfile.getPath());
GFile rootDir = subFS.lookup("/");
assertNotNull(rootDir);
assertEquals("/", rootDir.getPath());
assertEquals(FSUtilities.normalizeNativePath(workDir.getPath()),
rootDir.getFSRL().getPath());
rootDir = subFS.lookup(null);
assertNotNull(rootDir);
assertEquals("/", rootDir.getPath());
assertEquals(FSUtilities.normalizeNativePath(workDir.getPath()),
rootDir.getFSRL().getPath());
GFile baseDir = subFS.lookup("/sub");
assertNotNull(baseDir);
}
}
} }