diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java index 07d22f2e12..6bc3daf4ff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java @@ -15,9 +15,7 @@ */ package ghidra.app.util.opinion; -import java.io.File; import java.io.IOException; -import java.nio.file.Path; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -712,7 +710,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader Program libraryProgram = null; String simpleLibraryName = FilenameUtils.getName(library); - boolean isAbsolute = new File(library).isAbsolute(); + boolean isAbsolute = isAbsoluteLibraryPath(library); boolean success = false; try { @@ -847,13 +845,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader try { for (LibrarySearchPath searchPath : searchPaths) { monitor.checkCancelled(); - String fullLibraryPath = joinPaths(searchPath.relativeFsPath(), library); + String fullLibraryPath = + FSUtilities.appendPath(searchPath.relativeFsPath(), library); GFileSystem fs = searchPath.fsRef().getFilesystem(); FSRL fsrl = resolveLibraryFile(fs, fullLibraryPath); Optional.ofNullable(fsrl).ifPresent(results::add); } - if (results.isEmpty() && new File(library).isAbsolute()) { + if (results.isEmpty() && isAbsoluteLibraryPath(library)) { LocalFileSystem localFS = FileSystemService.getInstance().getLocalFS(); FSRL fsrl = resolveLibraryFile(localFS, library); Optional.ofNullable(fsrl).ifPresent(results::add); @@ -1054,7 +1053,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader * A library search path * * @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 */ protected record LibrarySearchPath(FileSystemRef fsRef, String relativeFsPath) {} @@ -1122,28 +1121,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader 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()); + try (RefdFile fileRef = fsService.getRefdFile(fsrl, monitor)) { + if (fileRef != null) { + result.add( + new LibrarySearchPath(fileRef.fsRef.dup(), fileRef.file.getPath())); } } - else { - try (RefdFile fileRef = fsService.getRefdFile(fsrl, monitor)) { - if (fileRef != null) { - File f = new File(fileRef.file.getPath()); // File API will sanitize Windows-style paths - result.add(new LibrarySearchPath(fileRef.fsRef.dup(), f.getPath())); - } - } - catch (IOException e) { - log.appendMsg(e.getMessage()); - } + catch (IOException e) { + log.appendMsg(e.getMessage()); } } success = true; @@ -1242,4 +1227,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader ? String.CASE_INSENSITIVE_ORDER : (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; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java index a8c1d57c72..a7afa5919c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java @@ -709,13 +709,6 @@ public class FileSystemService { if (ref != null) { 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. @@ -748,18 +741,6 @@ public class FileSystemService { 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. *

@@ -815,11 +796,6 @@ public class FileSystemService { public GFileSystem openFileSystemContainer(FSRL containerFSRL, TaskMonitor monitor) throws CancelledException, IOException { - GFileSystem subdirFS = probeForLocalSubDirFilesystem(containerFSRL); - if (subdirFS != null) { - return subdirFS; - } - ByteProvider byteProvider = getByteProvider(containerFSRL, true, monitor); return fsFactoryMgr.probe(byteProvider, this, null, FileSystemInfo.PRIORITY_LOWEST, monitor); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileLocal.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileLocal.java deleted file mode 100644 index 8a7f77b121..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileLocal.java +++ /dev/null @@ -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. - *

- * 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); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystem.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystem.java index ac7a9bcd3a..00b110f2c3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystem.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystem.java @@ -74,21 +74,6 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider { 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. * @@ -136,7 +121,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider { return fsFSRL.withPath(fsrlPath); } - private GFile getGFile(File f) { + public GFile getGFile(File f) { List parts = LocalFileSystem.getFilePathParts(f); // [/subdir/subroot/file, /subdir/subroot, /subdir, /] GFile current = rootDir; for (int i = parts.size() - 2; i >= 0; i--) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystemSub.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystemSub.java deleted file mode 100644 index 745d78d4a5..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystemSub.java +++ /dev/null @@ -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. - *

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

- * 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 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 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 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 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 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); - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBComponentProvider.java index 5cfd1606d6..2b8eb4a1c4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBComponentProvider.java @@ -19,6 +19,7 @@ import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*; import java.awt.Component; import java.awt.event.MouseEvent; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -42,6 +43,7 @@ import ghidra.app.services.ProgramManager; import ghidra.app.util.bin.ByteProvider; import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.fileinfo.FileAttributes; +import ghidra.framework.main.FrontEndTool; import ghidra.framework.model.*; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.plugin.importer.ImporterUtilities; @@ -81,20 +83,20 @@ public class FSBComponentProvider extends ComponentProviderAdapter * ownership of the passed-in {@link FileSystemRef fsRef}. * * @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) { - super(plugin.getTool(), getDescriptiveFSName(fsRef.getFilesystem()), plugin.getName()); + public FSBComponentProvider(FileSystemBrowserPlugin plugin, RefdFile rootDir) { + super(plugin.getTool(), getDescriptiveFSName(rootDir), plugin.getName()); this.plugin = plugin; - this.rootNode = new FSBRootNode(fsRef); + this.rootNode = new FSBRootNode(rootDir); this.pm = plugin.getTool().getService(ProgramManager.class); setTransient(); - setIcon(getFSIcon(fsRef.getFilesystem(), true, fsbIcons)); + setIcon(getFSIcon(rootDir.fsRef.getFilesystem(), true, fsbIcons)); initTree(); - fsRef.getFilesystem().getRefManager().addListener(this); + rootDir.fsRef.getFilesystem().getRefManager().addListener(this); initFileHandlers(); setHelpLocation( @@ -150,8 +152,12 @@ public class FSBComponentProvider extends ComponentProviderAdapter if (value instanceof FSBRootNode) { // do nothing } - else if (value instanceof FSBDirNode) { - // do nothing special, but exclude FSBFileNode + else if (value instanceof FSBDirNode dirNode) { + 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) { renderFile(fileNode, selected); @@ -367,6 +373,9 @@ public class FSBComponentProvider extends ComponentProviderAdapter } public boolean ensureFileAccessable(FSRL fsrl, FSBNode node, TaskMonitor monitor) { + if (node instanceof FSBDirNode) { + return true; + } FSBFileNode fileNode = (node instanceof FSBFileNode) ? (FSBFileNode) node : null; @@ -399,9 +408,16 @@ public class FSBComponentProvider extends ComponentProviderAdapter } 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) { return false; } + FSRL fsrl = fileNode.getFSRL(); gTree.runTask(monitor -> { if (!ensureFileAccessable(fsrl, fileNode, monitor)) { @@ -437,7 +453,8 @@ public class FSBComponentProvider extends ComponentProviderAdapter 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(); GTreeNode parent = modelFileNode.getParent(); @@ -453,7 +470,7 @@ public class FSBComponentProvider extends ComponentProviderAdapter contextChanged(); } else { - plugin.createNewFileSystemBrowser(ref, true); + plugin.createNewFileSystemBrowser(ref, null, true); } }); return true; @@ -577,6 +594,18 @@ public class FSBComponentProvider extends ComponentProviderAdapter if (destNode != null) { 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; } @@ -592,15 +621,23 @@ public class FSBComponentProvider extends ComponentProviderAdapter } - static String getDescriptiveFSName(GFileSystem fs) { - return fs instanceof LocalFileSystem ? "My Computer" : fs.getName(); + static String getDescriptiveFSName(RefdFile rootFile) { + 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) { List overlays = !isRootNode ? List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON) : List.of(); FSRL container = fs.getFSRL().getContainer(); String containerName = container != null ? container.getName() : "/"; - Icon image = fs instanceof LocalFileSystem || fs instanceof LocalFileSystemSub + Icon image = fs instanceof LocalFileSystem ? FSBIcons.MY_COMPUTER : fsbIcons.getIcon(containerName, overlays); if (image == FSBIcons.DEFAULT_ICON) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBIcons.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBIcons.java index 71d9badce0..187dbbd975 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBIcons.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBIcons.java @@ -141,7 +141,7 @@ public class FSBIcons { return buildIcon(DEFAULT_ICON, overlays); } - private Icon buildIcon(Icon base, List overlays) { + public static Icon buildIcon(Icon base, List overlays) { if (overlays == null || overlays.isEmpty()) { return base; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBRootNode.java index 3a7c3600c4..fded6f8da6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBRootNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBRootNode.java @@ -38,30 +38,30 @@ import ghidra.util.task.TaskMonitor; */ 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 FSBRootNode modelNode; private boolean cryptoStatusUpdated; private Icon icon; - FSBRootNode(FileSystemRef fsRef) { - this(fsRef, null); + FSBRootNode(RefdFile rootDir) { + this(rootDir, null); } - FSBRootNode(FileSystemRef fsRef, FSBFileNode prevNode) { - super(FSBComponentProvider.getDescriptiveFSName(fsRef.getFilesystem())); + FSBRootNode(RefdFile rootDir, FSBFileNode prevNode) { + super(FSBComponentProvider.getDescriptiveFSName(rootDir)); - this.fsRef = fsRef; + this.rootDir = rootDir; this.prevNode = prevNode; this.modelNode = this; - this.icon = FSBComponentProvider.getFSIcon(fsRef.getFilesystem(), prevNode == null, + this.icon = FSBComponentProvider.getFSIcon(rootDir.fsRef.getFilesystem(), prevNode == null, FSBIcons.getInstance()); } @Override public GTreeNode clone() throws CloneNotSupportedException { 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; } @@ -103,19 +103,19 @@ public class FSBRootNode extends FSBNode { @Override public GFile getGFile() { - return fsRef.getFilesystem().getRootDir(); + return rootDir.file; } public FileSystemRef getFSRef() { - return modelNode.fsRef; + return modelNode.rootDir.fsRef; } private void releaseFSRefIfModelNode() { if (this != modelNode) { return; } - FileSystemService.getInstance().releaseFileSystemImmediate(fsRef); - fsRef = null; + FileSystemService.getInstance().releaseFileSystemImmediate(rootDir.fsRef); + rootDir = null; } @Override @@ -142,10 +142,9 @@ public class FSBRootNode extends FSBNode { @Override public List generateChildren(TaskMonitor monitor) throws CancelledException { - if (fsRef != null) { + if (rootDir != null) { try { - return FSBNode.createNodesFromFileList(fsRef.getFilesystem().getListing(null), - monitor); + return FSBNode.createNodesFromFileList(rootDir.file.getListing(), monitor); } catch (IOException e) { FSUtilities.displayException(this, null, "Error Opening File System", @@ -157,15 +156,18 @@ public class FSBRootNode extends FSBNode { @Override public FSRL getFSRL() { - return modelNode != null && modelNode.fsRef != null - ? modelNode.fsRef.getFilesystem().getFSRL() + return modelNode != null && modelNode.rootDir != null + ? modelNode.rootDir.file.getFSRL() : null; } public FSBNode getGFileFSBNode(GFile file, TaskMonitor monitor) { List pathParts = splitGFilePath(file); + List rootPathParts = splitGFilePath(rootDir.file); + // TODO: ensure pathParts has a prefix that equals rootPathParts 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 { fileNode = fileNode.findMatchingNode(pathParts.get(i), monitor); } @@ -177,10 +179,11 @@ public class FSBRootNode extends FSBNode { } public FSRL getContainer() { - // use the rootDir's FSRL to sidestep issue with LocalFileSystemSub's non-standard fsFSRL - return fsRef != null - ? fsRef.getFilesystem().getRootDir().getFSRL().getFS().getContainer() - : null; + // allows the import of the file container of a filesystem image + if ( rootDir != null && rootDir.file.getParentFile() == null ) { + return rootDir.fsRef.getFilesystem().getFSRL().getContainer(); + } + return null; } private List splitGFilePath(GFile f) { @@ -193,8 +196,8 @@ public class FSBRootNode extends FSBNode { } public FSRL getProgramProviderFSRL(FSRL fsrl) { - if (fsRef != null) { - GFileSystem fs = fsRef.getFilesystem(); + if (rootDir != null) { + GFileSystem fs = rootDir.fsRef.getFilesystem(); if (fs instanceof GFileSystemProgramProvider programProviderFS) { try { GFile gfile = fs.lookup(fsrl.getPath()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java index 3ff4a20cbb..a6b6a1cedb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java @@ -140,22 +140,28 @@ public class FileSystemBrowserPlugin extends Plugin * method). * * @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 */ - public void createNewFileSystemBrowser(FileSystemRef fsRef, boolean show) { - Swing.runIfSwingOrRunLater(() -> doCreateNewFileSystemBrowser(fsRef, show)); + public void createNewFileSystemBrowser(FileSystemRef fsRef, GFile rootDir, boolean 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) { - FSRLRoot fsFSRL = fsRef.getFilesystem().getFSRL(); - FSBComponentProvider provider = currentBrowsers.get(fsFSRL); + private void doCreateNewFileSystemBrowser(RefdFile rootFile, boolean show) { + FSRL rootFSRL = rootFile.file.getFSRL(); + FSBComponentProvider provider = currentBrowsers.get(rootFSRL); if (provider != null) { - Msg.info(this, "Filesystem browser already open for " + fsFSRL); - fsRef.close(); + Msg.info(this, "Filesystem browser already open for " + rootFSRL); + FSUtilities.uncheckedClose(rootFile, null); } else { - provider = new FSBComponentProvider(this, fsRef); - currentBrowsers.put(fsFSRL, provider); + provider = new FSBComponentProvider(this, rootFile); + currentBrowsers.put(rootFSRL, provider); getTool().addComponentProvider(provider, false); provider.afterAddedToTool(); provider.contextChanged(); @@ -201,32 +207,6 @@ public class FileSystemBrowserPlugin extends Plugin 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 * filesystem browser and then displays that filesystem in a new fsb browser. @@ -255,12 +235,45 @@ public class FileSystemBrowserPlugin extends Plugin 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) -> { + FSRL containerFSRL = localFS.getLocalFSRL(file); 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() { // use a delayed initialization so we don't force the FileSystemService to initialize if (fsService == null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/ListMountedFSBFileHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/ListMountedFSBFileHandler.java index 1961485c41..6d78533eb8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/ListMountedFSBFileHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/ListMountedFSBFileHandler.java @@ -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. @@ -52,7 +52,9 @@ public class ListMountedFSBFileHandler implements FSBFileHandler { FileSystemRef fsRef; if (fsFSRL != null && (fsRef = context.fsService().getMountedFilesystem(fsFSRL)) != null) { - context.fsbComponent().getPlugin().createNewFileSystemBrowser(fsRef, true); + context.fsbComponent() + .getPlugin() + .createNewFileSystemBrowser(fsRef, null, true); } }) .build()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/OpenFsFSBFileHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/OpenFsFSBFileHandler.java index a4ea7c07aa..f626b9ab61 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/OpenFsFSBFileHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/filehandlers/OpenFsFSBFileHandler.java @@ -19,7 +19,6 @@ import java.util.List; import docking.action.DockingAction; import docking.action.builder.ActionBuilder; -import ghidra.formats.gfilesystem.FileSystemRef; import ghidra.formats.gfilesystem.FileSystemService; 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_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"; private FSBFileHandlerContext context; @@ -59,7 +59,18 @@ public class OpenFsFSBFileHandler implements FSBFileHandler { ac.getSelectedNode() instanceof FSBFileNode fileNode && fileNode.isLeaf() && !fileNode.isSymlink()) .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") .onAction( ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), false)) @@ -72,9 +83,9 @@ public class OpenFsFSBFileHandler implements FSBFileHandler { .toolBarGroup("B") .onAction(ac -> { FileSystemService fsService = context.fsService(); - FileSystemRef fsRef = - fsService.getMountedFilesystem(fsService.getLocalFS().getFSRL()); - context.plugin().createNewFileSystemBrowser(fsRef, true); + context.plugin() + .createNewFileSystemBrowser( + fsService.getLocalFS().getRefManager().create(), null, true); }) .build(), @@ -84,8 +95,7 @@ public class OpenFsFSBFileHandler implements FSBFileHandler { .toolBarIcon(FSBIcons.OPEN_FILE_SYSTEM) .toolBarGroup("B") .onAction(ac -> context.plugin().openFileSystem()) - .build() - ); + .build()); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/LocalGFileSystemTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/LocalGFileSystemTest.java index 8341eeda7d..bbe80da68c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/LocalGFileSystemTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/LocalGFileSystemTest.java @@ -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. @@ -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); - } - } - }