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 c353972b1b..bc71d42e46 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 @@ -1181,27 +1181,20 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader */ protected FSRL resolveLibraryFile(GFileSystem fs, Path libraryParentPath, String libraryName) throws IOException { - GFile libraryParentDir = fs.lookup( - libraryParentPath != null ? FilenameUtils.separatorsToUnix(libraryParentPath.toString()) - : null); - boolean compareWithoutExtension = isOptionalLibraryFilenameExtensions() && - FilenameUtils.getExtension(libraryName).equals(""); - if (libraryParentDir != null) { - Comparator libNameComparator = getLibraryNameComparator(); - for (GFile file : fs.getListing(libraryParentDir)) { - if (file.isDirectory()) { - continue; - } - String compareName = file.getName(); - if (compareWithoutExtension) { - compareName = FilenameUtils.getBaseName(compareName); - } - if (libNameComparator.compare(libraryName, compareName) == 0) { - return file.getFSRL(); - } - } - } - return null; + String lpp = libraryParentPath != null + ? FilenameUtils.separatorsToUnix(libraryParentPath.toString()) + : null; + String targetPath = FSUtilities.appendPath(lpp, libraryName); + + Comparator baseNameComp = getLibraryNameComparator(); + Comparator nameComp = isOptionalLibraryFilenameExtensions() && + FilenameUtils.getExtension(libraryName).isEmpty() + ? (s1, s2) -> baseNameComp.compare(FilenameUtils.getBaseName(s1), + FilenameUtils.getBaseName(s2)) + : baseNameComp; + + GFile foundFile = fs.lookup(targetPath, nameComp); + return foundFile != null && !foundFile.isDirectory() ? foundFile.getFSRL() : null; } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/AbstractFileSystem.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/AbstractFileSystem.java index 01abcb9296..f7d7d39f3e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/AbstractFileSystem.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/AbstractFileSystem.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. @@ -66,6 +66,11 @@ public abstract class AbstractFileSystem implements GFileSystem { return fsIndex.lookup(null, path, getFilenameComparator()); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndex.lookup(null, path, nameComp); + } + @Override public GFile getRootDir() { return fsIndex.getRootDir(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystem.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystem.java index 865ce2d158..f43e42e193 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystem.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystem.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. @@ -16,6 +16,7 @@ package ghidra.formats.gfilesystem; import java.io.*; +import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; @@ -23,6 +24,7 @@ import ghidra.app.util.bin.ByteProviderInputStream; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; import ghidra.formats.gfilesystem.fileinfo.FileAttribute; import ghidra.formats.gfilesystem.fileinfo.FileAttributes; +import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -129,6 +131,26 @@ public interface GFileSystem extends Closeable, ExtensionPoint { */ GFile lookup(String path) throws IOException; + /** + * Retrieves a {@link GFile} from this filesystem based on its full path and filename, using + * the specified name comparison logic (eg. case sensitive vs insensitive). + *

+ * @param path string path and filename of a file located in this filesystem. Use + * {@code null} or "/" to retrieve the root directory + * @param nameComp string comparator used to compare filenames. Use {@code null} to specify + * the file system's native comparison logic. + * @return {@link GFile} instance of requested file, null if not found. + * @throws IOException if IO error when looking up file. + */ + default GFile lookup(String path, Comparator nameComp) throws IOException { + // If you are seeing this error in your log file, it means your GFileSystem needs to be + // updated to implement this lookup() method. + Msg.error(GFileSystem.class, + "Unimplemented %s.lookup(path, comparator), falling back to non-comparator lookup" + .formatted(this.getClass().getSimpleName())); + return lookup(path); + } + /** * Returns the file system's root directory. *

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java index d510773f9c..e32468f427 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.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. @@ -165,10 +165,14 @@ public abstract class GFileSystemBase implements GFileSystem { @Override public GFile lookup(String path) throws IOException { + return lookup(path, getFilenameComparator()); + } + + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { if (path == null || path.equals("/")) { return root; } - Comparator nameComp = getFilenameComparator(); GFile current = root; String[] parts = path.split("/"); 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 ddf23e24c3..3ac202ae72 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 @@ -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. @@ -213,7 +213,12 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider { @Override public GFile lookup(String path) throws IOException { - File f = lookupFile(null, path, null); + return lookup(path, null); + } + + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + File f = lookupFile(null, path, nameComp); return f != null ? GFileImpl.fromPathString(this, FSUtilities.normalizeNativePath(f.getPath()), null, f.isDirectory(), f.length()) : null; } @@ -327,24 +332,8 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider { // If not using a comparator, or if the requested path is a // root element (eg "/", or "c:\\"), don't do per-directory-path lookups. - if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { - // On windows, getCanonicalFile() will return a corrected path using the case of - // the file element on the file system (eg. "c:/users" -> "c:/Users"), if the - // element exists. - // We don't want to do this on unix-ish file systems as it will follow symlinks - f = f.getCanonicalFile(); - } - return FSUtilities.isSymlink(f) || f.exists() ? f : null; - } - - // Test the file's path using the name comparator - if (f.exists() && baseDir == null) { - // try to short-cut by comparing the entire path string - File canonicalFile = f.getCanonicalFile(); - if (nameComp.compare(path, - FSUtilities.normalizeNativePath((canonicalFile.getPath()))) == 0) { - return canonicalFile; - } + f = updateCaseInsensitiveFilePath(f); + return (FSUtilities.isSymlink(f) || f.exists()) ? f : null; } // For path "/subdir/file", pathParts will contain, in reverse order: @@ -375,7 +364,28 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider { } } - static File findInDir(File dir, String name, Comparator nameComp) { + static File updateCaseInsensitiveFilePath(File f) throws IOException { + // On Windows, getCanonicalFile() will return a corrected path using the case of + // the actual file element on the file system (eg. "c:/users" -> "c:/Users"), if the + // element exists. + return OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS + ? f.getCanonicalFile() + : f; + } + + static File findInDir(File dir, String name, Comparator nameComp) throws IOException { + File exact = new File(dir, name); + if (exact.exists()) { + // Skip listing entire dir contents if exact match exists and the match agrees with + // the caller's comparator. The comparator could reject this test, for example + // if it was an exact case compare and the requested filename's case didn't match + // the actual file's case (on windows). + exact = updateCaseInsensitiveFilePath(exact); + if (nameComp.compare(exact.getName(), name) == 0) { + return exact; + } + } + // Searches for "name" in the list of files found in the directory. // Because a case-insensitive comparator could match on several files in the same directory, // query for all the files before picking a match: either an exact string match, or @@ -393,7 +403,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider { } } } - Collections.sort(candidateMatches); + Collections.sort(candidateMatches); // ensures stable results regardless if OS mutates order of listing on different runs return !candidateMatches.isEmpty() ? candidateMatches.get(0) : null; } 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 index 17748e44bf..61287a25bc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystemSub.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/LocalFileSystemSub.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. @@ -16,8 +16,7 @@ package ghidra.formats.gfilesystem; import java.io.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import ghidra.app.util.bin.ByteProvider; import ghidra.formats.gfilesystem.fileinfo.FileAttributes; @@ -155,8 +154,13 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider { @Override public GFile lookup(String path) throws IOException { - File f = LocalFileSystem.lookupFile(localfsRootDir, path, null); - if ( f == null ) { + 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); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java index d8be26e7a7..79d0b8e48d 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.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. @@ -197,6 +197,11 @@ public class CartFileSystem implements GFileSystem { return fsIndexHelper.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndexHelper.lookup(null, path, nameComp); + } + /** * Helper function to create byte provider for CaRT payload content that is * decompressed and decrypted. diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java index 83724ae277..eb90437356 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.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. @@ -17,6 +17,7 @@ package ghidra.file.formats.complzss; import java.io.IOException; import java.io.InputStream; +import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; @@ -99,4 +100,9 @@ public class CompLzssFileSystem implements GFileSystem { return fsIndex.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndex.lookup(null, path, nameComp); + } + } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java index 33c72b0ea8..d515ca331e 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.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. @@ -140,6 +140,11 @@ public class GZipFileSystem implements GFileSystem { return fsIndex.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndex.lookup(null, path, nameComp); + } + @Override public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException, CancelledException { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/img2/Img2FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/img2/Img2FileSystem.java index fa4da92201..6aaca17cb2 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/img2/Img2FileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/img2/Img2FileSystem.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. @@ -16,6 +16,7 @@ package ghidra.file.formats.ios.img2; import java.io.IOException; +import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; @@ -106,4 +107,9 @@ public class Img2FileSystem implements GFileSystem { return fsIndexHelper.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndexHelper.lookup(null, path, nameComp); + } + } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java index d22a0398e1..a3d10e196f 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.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. @@ -16,6 +16,7 @@ package ghidra.file.formats.java; import java.io.*; +import java.util.Comparator; import java.util.List; import org.apache.commons.io.FilenameUtils; @@ -134,6 +135,11 @@ public class JavaClassDecompilerFileSystem implements GFileSystem { return fsIndexHelper.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndexHelper.lookup(null, path, nameComp); + } + @Override public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException, CancelledException { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzfse/LzfseFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzfse/LzfseFileSystem.java index e04bb6b894..c97bf985f0 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzfse/LzfseFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzfse/LzfseFileSystem.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. @@ -17,6 +17,7 @@ package ghidra.file.formats.lzfse; import java.io.File; import java.io.IOException; +import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; @@ -111,4 +112,9 @@ public class LzfseFileSystem implements GFileSystem { public GFile lookup(String path) throws IOException { return fsIndex.lookup(path); } + + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndex.lookup(null, path, nameComp); + } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sparseimage/SparseImageFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sparseimage/SparseImageFileSystem.java index 6191d1cf4a..36950032ca 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sparseimage/SparseImageFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sparseimage/SparseImageFileSystem.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. @@ -18,6 +18,7 @@ package ghidra.file.formats.sparseimage; import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*; import java.io.IOException; +import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; @@ -96,6 +97,11 @@ public class SparseImageFileSystem implements GFileSystem { return fsIndexHelper.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsIndexHelper.lookup(null, path, nameComp); + } + private ByteProvider getPayload(FSRL payloadFSRL, TaskMonitor monitor) throws CancelledException, IOException { return fsService.getDerivedByteProviderPush(byteProvider.getFSRL(), payloadFSRL, "sparse", diff --git a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java index 74775f3445..2bca41437d 100644 --- a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java +++ b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java @@ -16,6 +16,7 @@ package skeleton; import java.io.IOException; +import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; @@ -112,6 +113,11 @@ public class SkeletonFileSystem implements GFileSystem { return fsih.lookup(path); } + @Override + public GFile lookup(String path, Comparator nameComp) throws IOException { + return fsih.lookup(null, path, nameComp); + } + @Override public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException, CancelledException {