GP-3654 refactor GFilesystem, add abstract base class, filename case

Add AbstractFileSystem to reduce duplicate boilerplate.
Start to add support for file name/path case (in)sensitive comparisons.
This commit is contained in:
dev747368 2023-07-20 20:03:29 +00:00
parent b0e0c7372a
commit 1b1fbf3baa
23 changed files with 567 additions and 523 deletions

View File

@ -0,0 +1,78 @@
/* ###
* 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.util.Comparator;
import java.util.List;
/**
* Default implementation of base file system functionality.
*
* @param <METADATATYPE> the type of objects that will be stored in the FileSystemIndexHelper
*/
public abstract class AbstractFileSystem<METADATATYPE> implements GFileSystem {
protected final FileSystemService fsService;
protected final FSRLRoot fsFSRL;
protected FileSystemIndexHelper<METADATATYPE> fsIndex;
protected FileSystemRefManager refManager = new FileSystemRefManager(this);
/**
* Initializes the fields for this abstract implementation of a file system.
*
* @param fsFSRL {@link FSRLRoot} of this file system
* @param fsService reference to the {@link FileSystemService} instance
*/
protected AbstractFileSystem(FSRLRoot fsFSRL, FileSystemService fsService) {
this.fsService = fsService;
this.fsFSRL = fsFSRL;
this.fsIndex = new FileSystemIndexHelper<>(this, fsFSRL);
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
protected Comparator<String> getFilenameComparator() {
return null; // null will cause exact matches in the fsIndex.lookup()
}
@Override
public GFile lookup(String path) {
return fsIndex.lookup(null, path, getFilenameComparator());
}
@Override
public List<GFile> getListing(GFile directory) {
return fsIndex.getListing(directory);
}
@Override
public int getFileCount() {
return fsIndex.getFileCount();
}
}

View File

@ -160,15 +160,32 @@ public class FileSystemIndexHelper<METADATATYPE> {
* @return {@link GFile} instance or null if no file was added to the index at that path
*/
public synchronized GFile lookup(String path) {
return lookup(null, path, null);
}
/**
* Mirror's {@link GFileSystem#lookup(String)} interface, with additional parameters to
* control the lookup.
*
* @param baseDir optional starting directory to perform lookup
* @param path path and filename of a file to find
* @param nameComp optional {@link Comparator} that compares file names. Suggested values are
* {@code String::compareTo} or {@code String::compareToIgnoreCase} or {@code null} (also exact).
* @return {@link GFile} instance or null if no file was added to the index at that path
*/
public synchronized GFile lookup(GFile baseDir, String path, Comparator<String> nameComp) {
String[] nameparts = (path != null ? path : "").split("/");
GFile parent = lookupParent(nameparts);
GFile parent = lookupParent(baseDir, nameparts, false, nameComp);
if (parent == null) {
return null;
}
String name = (nameparts.length > 0) ? nameparts[nameparts.length - 1] : null;
if (name == null || name.isEmpty()) {
return parent;
}
Map<String, FileData<METADATATYPE>> dirListing = getDirectoryContents(parent, false);
FileData<METADATATYPE> fileData = (dirListing != null) ? dirListing.get(name) : null;
FileData<METADATATYPE> fileData =
lookupFileInDir(getDirectoryContents(parent, false), name, nameComp);
return (fileData != null) ? fileData.file : null;
}
@ -197,7 +214,7 @@ public class FileSystemIndexHelper<METADATATYPE> {
long length, METADATATYPE metadata) {
String[] nameparts = path.replaceAll("[\\\\]", "/").split("/");
GFile parent = lookupParent(nameparts);
GFile parent = lookupParent(rootDir, nameparts, true, null);
String lastpart = nameparts[nameparts.length - 1];
FileData<METADATATYPE> fileData =
@ -301,19 +318,27 @@ public class FileSystemIndexHelper<METADATATYPE> {
* Walks a list of names of directories in nameparts (stopping prior to the last element)
* starting at the root of the filesystem and returns the final directory.
* <p>
* Directories in a path that have not been encountered before (ie. a file's path references a directory
* that hasn't been mentioned yet as its own file entry) will have a stub entry GFile created for them.
* Directories in a path that have not been encountered before (ie. a file's path references
* a directory that hasn't been mentioned yet as its own file entry) will have a stub entry
* GFile created for them if createIfMissing is true.
* <p>
* Superfluous slashes in the original filename (ie. name/sub//subafter_extra_slash) will
* be represented as empty string elements in the nameparts array and will be skipped
* as if they were not there.
* <p>
* @param nameparts
* @return
* @param baseDir optional starting directory to perform lookups
* @param nameparts String[] containing the elements of a path
* @param createIfMissing boolean flag, if true missing elements will have stub entries created
* for them
* @param nameComp optional comparator that will compare names, usually case-sensitive vs case
* insensitive
* @return GFile that represents the parent directory, or null if in read-only mode and not
* found
*/
protected GFile lookupParent(String[] nameparts) {
protected GFile lookupParent(GFile baseDir, String[] nameparts, boolean createIfMissing,
Comparator<String> nameComp) {
GFile currentDir = rootDir;
GFile currentDir = Objects.requireNonNullElse(baseDir, rootDir);
for (int i = 0; i < nameparts.length - 1; i++) {
Map<String, FileData<METADATATYPE>> currentDirContents =
getDirectoryContents(currentDir, true);
@ -321,8 +346,11 @@ public class FileSystemIndexHelper<METADATATYPE> {
if (name.isEmpty()) {
continue;
}
FileData<METADATATYPE> fileData = currentDirContents.get(name);
FileData<METADATATYPE> fileData = lookupFileInDir(currentDirContents, name, nameComp);
if (fileData == null) {
if (!createIfMissing) {
return null;
}
fileData = doStoreMissingDir(name, currentDir);
}
currentDir = fileData.file;
@ -331,6 +359,30 @@ public class FileSystemIndexHelper<METADATATYPE> {
return currentDir;
}
protected FileData<METADATATYPE> lookupFileInDir(
Map<String, FileData<METADATATYPE>> dirContents, String filename,
Comparator<String> nameComp) {
if (dirContents == null) {
return null;
}
if (nameComp == null) {
// exact match
return dirContents.get(filename);
}
List<FileData<METADATATYPE>> candidateFiles = new ArrayList<>();
for (FileData<METADATATYPE> fd : dirContents.values()) {
if (nameComp.compare(filename, fd.file.getName()) == 0) {
if (fd.file.getName().equals(filename)) {
return fd;
}
candidateFiles.add(fd);
}
}
Collections.sort(candidateFiles,
(f1, f2) -> f1.file.getName().compareTo(f2.file.getName()));
return !candidateFiles.isEmpty() ? candidateFiles.get(0) : null;
}
/**
* Creates a new GFile instance, using per-filesystem custom logic.
* <p>

View File

@ -89,7 +89,8 @@ public class GFileImpl implements GFile {
parent = fromFilename(fileSystem, parent, split[i], true, -1, null);
}
if (fsrl == null) {
fsrl = getFSRLFromParent(fileSystem, parent, split[split.length - 1]);
String filename = split.length > 0 ? split[split.length - 1] : "/";
fsrl = getFSRLFromParent(fileSystem, parent, filename);
}
return new GFileImpl(fileSystem, parent, isDirectory, length, fsrl);
}

View File

@ -119,7 +119,8 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
}
/**
* Retrieves a {@link GFile} from this filesystem based on its full path and filename.
* Retrieves a {@link GFile} from this filesystem based on its full path and filename, using
* this filesystem's default name comparison logic (eg. case sensitive vs insensitive).
* <p>
* @param path string path and filename of a file located in this filesystem. Use
* {@code null} or "/" to retrieve the root directory

View File

@ -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;
@ -151,12 +152,24 @@ public abstract class GFileSystemBase implements GFileSystem {
}
}
/**
* Override to specify a file-system specific name comparator.
*
* @return {@link Comparator} such as {@link String#compareTo(String)} or
* {@link String#compareToIgnoreCase(String)}
*/
protected Comparator<String> getFilenameComparator() {
return String::compareTo;
}
@Override
public GFile lookup(String path) throws IOException {
if (path == null || path.equals("/")) {
return root;
}
GFile current = null;
Comparator<String> nameComp = getFilenameComparator();
GFile current = root;
String[] parts = path.split("/");
partloop: for (String part : parts) {
if (part.isEmpty()) {
@ -164,7 +177,7 @@ public abstract class GFileSystemBase implements GFileSystem {
}
List<GFile> listing = getListing(current);
for (GFile gf : listing) {
if (part.equals(gf.getName())) {
if (nameComp.compare(part, gf.getName()) == 0) {
current = gf;
continue partloop;
}

View File

@ -22,7 +22,6 @@ import java.nio.file.*;
import java.util.*;
import org.apache.commons.collections4.map.ReferenceMap;
import org.apache.commons.io.FilenameUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileByteProvider;
@ -30,6 +29,7 @@ import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemFactory;
import ghidra.formats.gfilesystem.factory.GFileSystemFactoryIgnore;
import ghidra.formats.gfilesystem.fileinfo.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -122,10 +122,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
* @return The {@link FSRL}
*/
public FSRL getLocalFSRL(File f) {
// We prepend a "/" to ensure that Windows-style paths (i.e. C:\) start with a "/". For
// unix-style paths, this redundant "/" will be dropped.
return fsFSRL.withPath(
FSUtilities.appendPath("/", FilenameUtils.separatorsToUnix(f.getAbsolutePath())));
return fsFSRL.withPath(FSUtilities.normalizeNativePath(f.getAbsolutePath()));
}
@Override
@ -149,8 +146,8 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
if (directory == null) {
for (File f : File.listRoots()) {
results.add(GFileImpl.fromFSRL(this, null, fsFSRL.withPath(f.getName()),
f.isDirectory(), -1));
FSRL rootElemFSRL = fsFSRL.withPath(FSUtilities.normalizeNativePath(f.getName()));
results.add(GFileImpl.fromFSRL(this, null, rootElemFSRL, f.isDirectory(), -1));
}
}
else {
@ -166,8 +163,9 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
for (File f : files) {
if (f.isFile() || f.isDirectory()) {
results.add(GFileImpl.fromFSRL(this, directory,
directory.getFSRL().appendPath(f.getName()), f.isDirectory(), f.length()));
FSRL newFileFSRL = directory.getFSRL().appendPath(f.getName());
results.add(GFileImpl.fromFSRL(this, directory, newFileFSRL, f.isDirectory(),
f.length()));
}
}
}
@ -226,11 +224,10 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
}
@Override
public GFileImpl lookup(String path) throws IOException {
File f = new File(path);
GFileImpl gf = GFileImpl.fromPathString(this, FilenameUtils.separatorsToUnix(f.getPath()),
null, f.isDirectory(), f.length());
return gf;
public GFile lookup(String path) throws IOException {
File f = lookupFile(null, path, null);
return f != null ? GFileImpl.fromPathString(this,
FSUtilities.normalizeNativePath(f.getPath()), null, f.isDirectory(), f.length()) : null;
}
@Override
@ -292,54 +289,121 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
//-----------------------------------------------------------------------------------
private static class FileFingerprintRec {
final String path;
final long timestamp;
final long length;
private record FileFingerprintRec(String path, long timestamp, long length) {
}
FileFingerprintRec(String path, long timestamp, long length) {
this.path = path;
this.timestamp = timestamp;
this.length = length;
//--------------------------------------------------------------------------------------------
/**
* Looks up a file, by its string path, using a custom comparator.
* <p>
* If any element of the path, or the filename are not found, returns a null.
* <p>
* A null custom comparator avoids testing each element of the directory path and instead
* relies on the native local file system's name matching.
*
* @param baseDir optional directory to start lookup at
* @param path String path
* @param nameComp optional {@link Comparator} that will compare filenames, or {@code null}
* to use native local file system lookup (eg. case-insensitive on windows)
* @return File that points to the requested path, or null if file was not present on the
* local filesystem (because it doesn't exist, or the name comparison function rejected it)
*/
public static File lookupFile(File baseDir, String path, Comparator<String> nameComp) {
// TODO: if path is in unc format "//server/share/path", linux jvm's will normalize the
// leading double slashes to a single "/". Should the path be rejected immediately in a
// non-windows jvm?
path = Objects.requireNonNullElse(path, "/");
File f = new File(baseDir, path); // null baseDir is okay
if (!f.isAbsolute()) {
Msg.debug(LocalFileSystem.class,
"Non-absolute path encountered in LocalFileSystem lookup: " + path);
// TODO: this would be better to throw an exception, but because some relative filenames
// have leaked into some FSRLs, resolving those paths (even if it produces an incorrect
// result) seems preferable.
f = f.getAbsoluteFile();
}
try {
if (nameComp == null || f.getParentFile() == null) {
// If not using a comparator, or if the requested path is a
// root element (eg "/", or "c:\\"), don't do per-directory-path lookups.
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (length ^ (length >>> 32));
result = prime * result + ((path == null) ? 0 : path.hashCode());
result = prime * result + (int) (timestamp ^ (timestamp >>> 32));
return result;
}
// 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.
return f.exists() ? f.getCanonicalFile() : null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof FileFingerprintRec)) {
return false;
}
FileFingerprintRec other = (FileFingerprintRec) obj;
if (length != other.length) {
return false;
}
if (path == null) {
if (other.path != null) {
return false;
if (f.exists()) {
// 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;
}
}
else if (!path.equals(other.path)) {
return false;
// For path "/subdir/file", pathParts will contain, in reverse order:
// [/subdir/file, /subdir, /]
// The root element ("/", or "c:/") will never be subjected to the name comparator
// The case of each element will be what was specified in the path parameter.
// Lookup each element in its parent directory, using the comparator to find the file
// in the full listing of each directory.
// If requested path has "." and ".." elements, findInDir() will not find them,
// avoiding path traversal issues.
// TODO: shouldn't use findInDir on the server and share parts of a UNC path "//server/share"
List<File> pathParts = getFilePathParts(f);
for (int i = pathParts.size() - 2 /*skip root ele*/; i >= 0; i--) {
File parentDir = pathParts.get(i + 1);
File part = pathParts.get(i);
File foundFile = findInDir(parentDir, part.getName(), nameComp);
if (foundFile == null) {
return null;
}
pathParts.set(i, foundFile);
}
if (timestamp != other.timestamp) {
return false;
}
return true;
return pathParts.get(0);
}
catch (IOException e) {
Msg.warn(LocalFileSystem.class, "Error resolving path: " + path, e);
return null;
}
}
static File findInDir(File dir, String name, Comparator<String> nameComp) {
// 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
// if there are several candidates, the first in the list after sorting.
File[] files = dir.listFiles();
List<File> candidateMatches = new ArrayList<>();
if (files != null) {
for (File f : files) {
String foundFilename = f.getName();
if (nameComp.compare(name, foundFilename) == 0) {
if (name.equals(foundFilename)) {
return f;
}
candidateMatches.add(f);
}
}
}
Collections.sort(candidateMatches);
return !candidateMatches.isEmpty() ? candidateMatches.get(0) : null;
}
static List<File> getFilePathParts(File f) {
// return a list of the parts of the specified file:
// "/subdir/file" -> "/subidr/file", "/subdir", "/"
// "c:/subdir/file" -> "c:/subdir/file", "c:/subdir", "c:/"
// "//uncserver/share/path" -> "//uncserver/share/path", "//uncserver/share", "//uncserver", "//"
// (windows jvm only, unix jvm will normalize a path's leading "//" to be "/"
List<File> results = new ArrayList<File>();
while (f != null) {
results.add(f);
f = f.getParentFile();
}
return results;
}
}

View File

@ -20,8 +20,6 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.exception.CancelledException;
@ -152,25 +150,39 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
@Override
public GFile lookup(String path) throws IOException {
path = StringUtils.defaultString(path, "/");
// Create a new GFile instance with a FSRL based on the RootFS (and not this FS),
File curFile = localfsRootDir;
GFileLocal result = rootGFile;
String[] parts = path.split("/");
for (String name : parts) {
if (name.isEmpty()) {
continue;
}
curFile = new File(curFile, name);
FSRL fsrl = result.getFSRL().appendPath(name);
String relPath = FSUtilities.appendPath(result.getPath(), name);
result = new GFileLocal(curFile, relPath, fsrl, this, result);
File f = LocalFileSystem.lookupFile(localfsRootDir, path, null);
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 {

View File

@ -49,7 +49,7 @@ public class SingleFileSystemIndexHelper {
// used as the owner of the new GFileImpl instances.
this.rootDir = GFileImpl.fromFSRL(fs, null, fsFSRL.withPath("/"), true, -1);
this.payloadFile = GFileImpl.fromFSRL(fs, rootDir,
rootDir.getFSRL().withPath(payloadFilename).withMD5(payloadMD5), false, length);
rootDir.getFSRL().appendPath(payloadFilename).withMD5(payloadMD5), false, length);
}
/**
@ -126,28 +126,43 @@ public class SingleFileSystemIndexHelper {
if (isClosed()) {
throw new IOException("Invalid state, index already closed");
}
if (directory == null || rootDir.equals(directory)) {
return Arrays.asList(payloadFile);
}
return Collections.emptyList();
return directory == null || rootDir.equals(directory) ? List.of(payloadFile) : List.of();
}
/**
* Mirror's {@link GFileSystem#lookup(String)} interface.
*
*
* @param path path and filename of a file to find (either "/" for root or the payload file's
* path).
* @return {@link GFile} instance or null if requested path is not the same as the payload file.
*/
public GFile lookup(String path) {
return lookup(null, path, null);
}
/**
* Mirror's {@link GFileSystem#lookup(String)} interface.
*
* @param baseDir starting directory
* @param path path and filename of a file to find (either "/" for root or the payload file's
* path).
* @param nameComp optional {@link Comparator} that compares file names. Suggested values are
* {@code String::compareTo} or {@code String::compareToIgnoreCase} or {@code null}.
* @return {@link GFile} instance or null if requested path is not the same as
* the payload file.
*/
public GFile lookup(String path) {
public GFile lookup(GFile baseDir, String path, Comparator<String> nameComp) {
if (baseDir != null && !baseDir.equals(rootDir)) {
return null;
}
if (path == null || path.equals("/")) {
return rootDir;
}
else if (path.equals(payloadFile.getFSRL().getPath())) {
return payloadFile;
}
return null;
nameComp = Objects.requireNonNullElse(nameComp, String::compareTo);
// compare the FSRL path ("/payloadname") as well just the payloadname (to be compatible
// with existing data that have malformed fsrls without a leading slash in the path)
return nameComp.compare(path, payloadFile.getFSRL().getPath()) == 0 ||
nameComp.compare(path, payloadFile.getFSRL().getName()) == 0 ? payloadFile : null;
}
@Override

View File

@ -0,0 +1,91 @@
/* ###
* 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 static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGenericTest;
public class LocalGFileSystemTest {
private File testTempDir;
private FileSystemService fsService;
private File workDir;
private LocalFileSystem localFS;
@Before
public void setup() throws IOException {
testTempDir = AbstractGenericTest.createTempDirectory("localfs_test");
fsService = new FileSystemService(new File(testTempDir, "cache"));
workDir = new File(testTempDir, "work");
workDir.mkdirs();
localFS = fsService.getLocalFS();
}
@Test
public void testBasePathLookups() throws IOException {
File subworkdir = new File(workDir, "sub/Sub2/SUB3");
subworkdir.mkdirs();
GFile sub = localFS.lookup(FSUtilities.normalizeNativePath(workDir.getPath() + "/sub"));
assertNotNull(sub);
GFile rootNull = localFS.lookup(null);
assertNotNull(rootNull);
GFile rootSlash = localFS.lookup("/");
assertNotNull(rootSlash);
}
@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);
}
}
}

View File

@ -19,7 +19,6 @@ import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
@ -32,18 +31,13 @@ import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "coff", description = "COFF Archive", factory = CoffArchiveFileSystemFactory.class)
public class CoffArchiveFileSystem implements GFileSystem {
private final FSRLRoot fsFSRL;
private FileSystemIndexHelper<CoffArchiveMemberHeader> fsih;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
public class CoffArchiveFileSystem extends AbstractFileSystem<CoffArchiveMemberHeader> {
private ByteProvider provider;
public CoffArchiveFileSystem(FSRLRoot fsFSRL, ByteProvider provider) {
this.fsFSRL = fsFSRL;
super(fsFSRL, FileSystemService.getInstance());
this.provider = provider;
this.fsih = new FileSystemIndexHelper<>(this, fsFSRL);
}
public void mount(TaskMonitor monitor) throws IOException {
@ -56,7 +50,7 @@ public class CoffArchiveFileSystem implements GFileSystem {
String name = camh.getName().replace('\\', '/');//replace stupid windows backslashes.
monitor.setMessage(name);
fsih.storeFile(name, fsih.getFileCount(), false, camh.getSize(), camh);
fsIndex.storeFile(name, fsIndex.getFileCount(), false, camh.getSize(), camh);
}
}
}
@ -65,19 +59,9 @@ public class CoffArchiveFileSystem implements GFileSystem {
}
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) {
CoffArchiveMemberHeader entry = fsih.getMetadata(file);
CoffArchiveMemberHeader entry = fsIndex.getMetadata(file);
return (entry != null && entry.isCOFF())
? new ByteProviderWrapper(provider, entry.getPayloadOffset(), entry.getSize(),
file.getFSRL())
@ -91,7 +75,7 @@ public class CoffArchiveFileSystem implements GFileSystem {
provider.close();
provider = null;
}
fsih.clear();
fsIndex.clear();
}
@Override
@ -99,14 +83,9 @@ public class CoffArchiveFileSystem implements GFileSystem {
return provider == null;
}
@Override
public int getFileCount() {
return fsih.getFileCount();
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
CoffArchiveMemberHeader entry = fsih.getMetadata(file);
CoffArchiveMemberHeader entry = fsIndex.getMetadata(file);
FileAttributes result = new FileAttributes();
if (entry != null) {
result.add(NAME_ATTR, entry.getName());
@ -118,19 +97,4 @@ public class CoffArchiveFileSystem implements GFileSystem {
}
return result;
}
@Override
public GFile lookup(String path) throws IOException {
return fsih.lookup(path);
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsih.getListing(directory);
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
}

View File

@ -18,7 +18,6 @@ package ghidra.file.formats.cpio;
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.*;
import java.util.List;
import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry;
import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
@ -29,37 +28,28 @@ import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
@FileSystemInfo(type = "cpio", description = "CPIO", factory = CpioFileSystemFactory.class)
public class CpioFileSystem implements GFileSystem {
private FileSystemService fsService;
private FSRLRoot fsFSRL;
private FileSystemIndexHelper<CpioArchiveEntry> fsIndex;
private FileSystemRefManager fsRefManager = new FileSystemRefManager(this);
public class CpioFileSystem extends AbstractFileSystem<CpioArchiveEntry> {
private ByteProvider provider;
public CpioFileSystem(FSRLRoot fsFSRL, ByteProvider provider, FileSystemService fsService,
TaskMonitor monitor)
throws IOException {
TaskMonitor monitor) throws IOException {
super(fsFSRL, fsService);
monitor.setMessage("Opening CPIO...");
this.fsService = fsService;
this.fsFSRL = fsFSRL;
this.provider = provider;
this.fsIndex = new FileSystemIndexHelper<>(this, fsFSRL);
try (CpioArchiveInputStream cpioInputStream =
new CpioArchiveInputStream(provider.getInputStream(0))) {
CpioArchiveEntry entry;
int fileNum = 0;
while ((entry = cpioInputStream.getNextCPIOEntry()) != null) {
FileUtilities.copyStreamToStream(cpioInputStream, OutputStream.nullOutputStream(),
monitor);
FSUtilities.streamCopy(cpioInputStream, OutputStream.nullOutputStream(), monitor);
monitor.setMessage(entry.getName());
fsIndex.storeFile(entry.getName(), fileNum++, entry.isDirectory(),
entry.getSize(), entry);
fsIndex.storeFile(entry.getName(), fileNum++, entry.isDirectory(), entry.getSize(),
entry);
}
}
catch (EOFException e) {
@ -75,7 +65,7 @@ public class CpioFileSystem implements GFileSystem {
@Override
public void close() throws IOException {
fsRefManager.onClose();
refManager.onClose();
fsIndex.clear();
if (provider != null) {
provider.close();
@ -83,36 +73,11 @@ public class CpioFileSystem implements GFileSystem {
}
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
}
@Override
public boolean isClosed() {
return provider == null;
}
@Override
public FileSystemRefManager getRefManager() {
return fsRefManager;
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsIndex.getListing(directory);
}
@Override
public GFile lookup(String path) throws IOException {
return fsIndex.lookup(path);
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();

View File

@ -20,7 +20,8 @@ import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.BitSet;
import java.util.Date;
import ghidra.app.util.bin.*;
import ghidra.formats.gfilesystem.*;
@ -33,13 +34,10 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "ext4", description = "EXT4", factory = Ext4FileSystemFactory.class)
public class Ext4FileSystem implements GFileSystem {
public class Ext4FileSystem extends AbstractFileSystem<Ext4File> {
public static final Charset EXT4_DEFAULT_CHARSET = StandardCharsets.UTF_8;
private FileSystemIndexHelper<Ext4File> fsih;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private FSRLRoot fsrl;
private int blockSize;
private ByteProvider provider;
private String volumeName;
@ -47,8 +45,7 @@ public class Ext4FileSystem implements GFileSystem {
private Ext4SuperBlock superBlock;
public Ext4FileSystem(FSRLRoot fsrl, ByteProvider provider) {
this.fsrl = fsrl;
this.fsih = new FileSystemIndexHelper<>(this, fsrl);
super(fsrl, FileSystemService.getInstance());
this.provider = provider;
}
@ -95,8 +92,8 @@ public class Ext4FileSystem implements GFileSystem {
monitor.initialize(usedInodeCount);
BitSet processedInodes = new BitSet(inodes.length);
processDirectory(inodes[Ext4Constants.EXT4_INODE_INDEX_ROOTDIR], fsih.getRootDir(), inodes,
processedInodes, monitor);
processDirectory(inodes[Ext4Constants.EXT4_INODE_INDEX_ROOTDIR], fsIndex.getRootDir(),
inodes, processedInodes, monitor);
checkUnprocessedInodes(inodes, processedInodes);
}
@ -160,8 +157,8 @@ public class Ext4FileSystem implements GFileSystem {
return;
}
GFile gfile = fsih.storeFileWithParent(name, parentDir, -1, inode.isDir(), inode.getSize(),
new Ext4File(name, inode));
GFile gfile = fsIndex.storeFileWithParent(name, parentDir, -1, inode.isDir(),
inode.getSize(), new Ext4File(name, inode));
if (processedInodes.get(inodeNumber)) {
// this inode was already seen and handled earlier. adding a second filename to the fsih is
// okay, but don't try to process as a directory, which shouldn't normally be possible
@ -174,21 +171,11 @@ public class Ext4FileSystem implements GFileSystem {
}
}
@Override
public int getFileCount() {
return fsih.getFileCount();
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsih.getListing(directory);
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();
Ext4File ext4File = fsih.getMetadata(file);
Ext4File ext4File = fsIndex.getMetadata(file);
if (ext4File != null) {
Ext4Inode inode = ext4File.getInode();
result.add(NAME_ATTR, ext4File.getName());
@ -237,7 +224,7 @@ public class Ext4FileSystem implements GFileSystem {
throw new IOException(
"Symlink too long: " + file.getPath() + ", " + symlinkDebugPath);
}
Ext4File extFile = fsih.getMetadata(currentFile);
Ext4File extFile = fsIndex.getMetadata(currentFile);
if (extFile == null) {
throw new IOException("Missing Ext4 metadata for " + currentFile.getPath());
}
@ -252,6 +239,7 @@ public class Ext4FileSystem implements GFileSystem {
if (currentFile.getParentFile() == null) {
throw new IOException("No parent file for " + currentFile);
}
// TODO: doesn't handle "../../" traversal yet
symlinkDestPath =
FSUtilities.appendPath(currentFile.getParentFile().getPath(),
symlinkDestPath);
@ -273,7 +261,7 @@ public class Ext4FileSystem implements GFileSystem {
}
private Ext4Inode getInodeFor(GFile file, TaskMonitor monitor) throws IOException {
Ext4File extFile = fsih.getMetadata(file);
Ext4File extFile = fsIndex.getMetadata(file);
if (extFile == null) {
return null;
}
@ -362,17 +350,12 @@ public class Ext4FileSystem implements GFileSystem {
refManager.onClose();
provider.close();
provider = null;
fsih.clear();
fsIndex.clear();
}
@Override
public String getName() {
return fsrl.getContainer().getName() + " - " + volumeName + " - " + uuid;
}
@Override
public FSRLRoot getFSRL() {
return fsrl;
return "%s - %s - %s".formatted(fsFSRL.getContainer().getName(), volumeName, uuid);
}
@Override
@ -380,14 +363,4 @@ public class Ext4FileSystem implements GFileSystem {
return provider == null;
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
@Override
public GFile lookup(String path) throws IOException {
return fsih.lookup(path);
}
}

View File

@ -136,7 +136,7 @@ public class GZipFileSystem implements GFileSystem {
}
@Override
public GFile lookup(String path) throws IOException {
public GFile lookup(String path) {
return fsIndex.lookup(path);
}

View File

@ -45,49 +45,46 @@ import ghidra.util.task.*;
* restarted.
*/
@FileSystemInfo(type = "dmg", description = "iOS Disk Image (DMG)", factory = DmgClientFileSystemFactory.class)
public class DmgClientFileSystem implements GFileSystem {
public class DmgClientFileSystem extends AbstractFileSystem<Object> {
private final FSRLRoot fsrl;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private FileSystemIndexHelper<Object> fsih;
private File decryptedDmgFile;
private boolean deleteFileWhenDone;
private DmgServerProcessManager processManager;
private CancelledListener listener = () -> processManager.interruptCmd();
private FileSystemService fsService;
/**
* Creates a {@link DmgClientFileSystem} instance, using a decrypted dmg file and
* the filesystem's {@link FSRLRoot}.
*
* @param decryptedDmgFile path to a decrypted DMG file. The DmgClientFileSystemFactory
* takes care of decrypting for us.
* @param fsrl {@link FSRLRoot} of this filesystem.
* takes care of decrypting for us
* @param deleteFileWhenDone boolean flag, if true, the container file will be deleted when
* the filesystem is closed
* @param fsrl {@link FSRLRoot} of this filesystem
* @param fsService {@link FileSystemService} reference
*/
public DmgClientFileSystem(File decryptedDmgFile, boolean deleteFileWhenDone, FSRLRoot fsrl,
FileSystemService fsService) {
this.fsrl = fsrl;
this.fsih = new FileSystemIndexHelper<>(this, fsrl);
super(fsrl, fsService);
this.decryptedDmgFile = decryptedDmgFile;
this.deleteFileWhenDone = deleteFileWhenDone;
this.fsService = fsService;
}
public void mount(TaskMonitor monitor) throws CancelledException, IOException {
processManager =
new DmgServerProcessManager(decryptedDmgFile, fsrl.getContainer().getName());
new DmgServerProcessManager(decryptedDmgFile, fsFSRL.getContainer().getName());
monitor.addCancelledListener(listener);
try {
UnknownProgressWrappingTaskMonitor upwtm =
new UnknownProgressWrappingTaskMonitor(monitor, 1);
recurseDirectories(fsih.getRootDir(), upwtm);
recurseDirectories(fsIndex.getRootDir(), upwtm);
}
finally {
monitor.removeCancelledListener(listener);
}
Msg.info(this,
"Indexed " + fsih.getFileCount() + " files in " + fsrl.getContainer().getName());
Msg.info(this, "Indexed %d files in %s".formatted(fsIndex.getFileCount(),
fsFSRL.getContainer().getName()));
}
@ -98,34 +95,19 @@ public class DmgClientFileSystem implements GFileSystem {
processManager.close();
processManager = null;
fsih.clear();
fsih = null;
fsIndex.clear();
fsIndex = null;
if (deleteFileWhenDone) {
Msg.info(this, "Deleting DMG temp file:" + decryptedDmgFile);
decryptedDmgFile.delete();
}
}
@Override
public String getName() {
return fsrl.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsrl;
}
@Override
public boolean isClosed() {
return processManager == null;
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
@ -162,7 +144,7 @@ public class DmgClientFileSystem implements GFileSystem {
monitor.incrementProgress(1);
// throw away the gfileimpl from getrawlisting(), create new gfile in rafi
GFile newF = fsih.storeFileWithParent(f.getName(), dir, -1, f.isDirectory(),
GFile newF = fsIndex.storeFileWithParent(f.getName(), dir, -1, f.isDirectory(),
f.getLength(), null);
if (newF.isDirectory()) {
recurseDirectories(newF, monitor);
@ -190,21 +172,6 @@ public class DmgClientFileSystem implements GFileSystem {
return results;
}
@Override
public GFile lookup(String path) throws IOException {
return fsih.lookup(path);
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsih.getListing(directory);
}
@Override
public int getFileCount() {
return fsih.getFileCount();
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
monitor.addCancelledListener(listener);

View File

@ -33,7 +33,7 @@ import ghidra.util.task.TaskMonitor;
//@formatter:on
public class Img2FileSystem implements GFileSystem {
private FSRLRoot fsFSRL;
private final FSRLRoot fsFSRL;
private SingleFileSystemIndexHelper fsIndexHelper;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private ByteProvider provider;
@ -102,7 +102,7 @@ public class Img2FileSystem implements GFileSystem {
}
@Override
public GFile lookup(String path) throws IOException {
public GFile lookup(String path) {
return fsIndexHelper.lookup(path);
}

View File

@ -30,20 +30,15 @@ import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "img3", description = "iOS " +
Img3Constants.IMG3_SIGNATURE, factory = Img3FileSystemFactory.class)
public class Img3FileSystem implements GFileSystem {
public class Img3FileSystem extends AbstractFileSystem<DataTag> {
private FSRLRoot fsFSRL;
private FileSystemRefManager fsRefManager = new FileSystemRefManager(this);
private FileSystemIndexHelper<DataTag> fsIndexHelper;
private ByteProvider provider;
private FileSystemService fsService;
public Img3FileSystem(FSRLRoot fsFSRL, ByteProvider provider, FileSystemService fsService,
TaskMonitor monitor) throws IOException {
this.fsFSRL = fsFSRL;
this.fsIndexHelper = new FileSystemIndexHelper<>(this, fsFSRL);
super(fsFSRL, fsService);
this.provider = provider;
this.fsService = fsService;
monitor.setMessage("Opening IMG3...");
Img3 header = new Img3(provider);
@ -61,7 +56,7 @@ public class Img3FileSystem implements GFileSystem {
DataTag dataTag = tags.get(i);
String filename = getDataTagFilename(dataTag, i, tags.size() > 1);
fsIndexHelper.storeFileWithParent(filename, fsIndexHelper.getRootDir(), i, false,
fsIndex.storeFileWithParent(filename, fsIndex.getRootDir(), i, false,
dataTag.getTotalLength(), dataTag);
}
}
@ -73,8 +68,8 @@ public class Img3FileSystem implements GFileSystem {
@Override
public void close() throws IOException {
fsRefManager.onClose();
fsIndexHelper.clear();
refManager.onClose();
fsIndex.clear();
if (provider != null) {
provider.close();
provider = null;
@ -90,7 +85,7 @@ public class Img3FileSystem implements GFileSystem {
"Unable to decrypt IMG3 data because IMG3 crypto keys are specific to the container it is embedded in and this IMG3 was not in a container");
}
DataTag dataTag = fsIndexHelper.getMetadata(file);
DataTag dataTag = fsIndex.getMetadata(file);
if (dataTag == null) {
throw new IOException("Unknown file: " + file);
}
@ -106,34 +101,9 @@ public class Img3FileSystem implements GFileSystem {
return null;
}
@Override
public List<GFile> getListing(GFile directory) {
return fsIndexHelper.getListing(directory);
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public boolean isClosed() {
return provider == null;
}
@Override
public FileSystemRefManager getRefManager() {
return fsRefManager;
}
@Override
public GFile lookup(String path) throws IOException {
return fsIndexHelper.lookup(path);
}
}

View File

@ -39,14 +39,15 @@ import utilities.util.FileUtilities;
@FileSystemInfo(type = "javaclass", description = "Java Class Decompiler", factory = JavaClassDecompilerFileSystemFactory.class, priority = FileSystemInfo.PRIORITY_LOW)
public class JavaClassDecompilerFileSystem implements GFileSystem {
private FSRLRoot fsFSRL;
private final FSRLRoot fsFSRL;
private final FileSystemService fsService;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private SingleFileSystemIndexHelper fsIndexHelper;
private ByteProvider provider;
private FSRL containerFSRL;
private String className;
private String javaSrcFilename;
private FileSystemService fsService;
public JavaClassDecompilerFileSystem(FSRLRoot fsFSRL, ByteProvider provider,
FileSystemService fsService, TaskMonitor monitor)
@ -128,7 +129,7 @@ public class JavaClassDecompilerFileSystem implements GFileSystem {
}
@Override
public GFile lookup(String path) throws IOException {
public GFile lookup(String path) {
return fsIndexHelper.lookup(path);
}

View File

@ -19,7 +19,6 @@ import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.omf.OmfFileHeader;
@ -30,18 +29,13 @@ import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "omf", description = "OMF Archive", factory = OmfArchiveFileSystemFactory.class)
public class OmfArchiveFileSystem implements GFileSystem {
private final FSRLRoot fsFSRL;
private FileSystemIndexHelper<OmfLibraryRecord.MemberHeader> fsih;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
public class OmfArchiveFileSystem extends AbstractFileSystem<OmfLibraryRecord.MemberHeader> {
private ByteProvider provider;
public OmfArchiveFileSystem(FSRLRoot fsFSRL, ByteProvider provider) {
this.fsFSRL = fsFSRL;
super(fsFSRL, FileSystemService.getInstance());
this.provider = provider;
this.fsih = new FileSystemIndexHelper<>(this, fsFSRL);
}
public void mount(TaskMonitor monitor) throws IOException {
@ -52,7 +46,7 @@ public class OmfArchiveFileSystem implements GFileSystem {
for (OmfLibraryRecord.MemberHeader member : memberHeaders) {
String name = member.name;
monitor.setMessage(name);
fsih.storeFile(name, fsih.getFileCount(), false, member.size, member);
fsIndex.storeFile(name, fsIndex.getFileCount(), false, member.size, member);
}
}
@ -63,17 +57,7 @@ public class OmfArchiveFileSystem implements GFileSystem {
provider.close();
provider = null;
}
fsih.clear();
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
fsIndex.clear();
}
@Override
@ -81,40 +65,20 @@ public class OmfArchiveFileSystem implements GFileSystem {
return provider == null;
}
@Override
public int getFileCount() {
return fsih.getFileCount();
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
@Override
public GFile lookup(String path) throws IOException {
return fsih.lookup(path);
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) {
OmfLibraryRecord.MemberHeader member = fsih.getMetadata(file);
OmfLibraryRecord.MemberHeader member = fsIndex.getMetadata(file);
return (member != null)
? new ByteProviderWrapper(provider, member.payloadOffset, member.size,
file.getFSRL())
: null;
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsih.getListing(directory);
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();
OmfLibraryRecord.MemberHeader entry = fsih.getMetadata(file);
OmfLibraryRecord.MemberHeader entry = fsIndex.getMetadata(file);
if (entry != null) {
result.add(NAME_ATTR, entry.name);
result.add(SIZE_ATTR, entry.size);

View File

@ -42,11 +42,7 @@ import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
@FileSystemInfo(type = "7zip", description = "7Zip", factory = SevenZipFileSystemFactory.class)
public class SevenZipFileSystem implements GFileSystem {
private FileSystemService fsService;
private FileSystemIndexHelper<ISimpleInArchiveItem> fsIndexHelper;
private FSRLRoot fsrl;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
public class SevenZipFileSystem extends AbstractFileSystem<ISimpleInArchiveItem> {
private Map<Integer, String> passwords = new HashMap<>();
private IInArchive archive;
@ -56,9 +52,7 @@ public class SevenZipFileSystem implements GFileSystem {
private ArchiveFormat archiveFormat;
public SevenZipFileSystem(FSRLRoot fsrl, FileSystemService fsService) {
this.fsService = fsService;
this.fsrl = fsrl;
this.fsIndexHelper = new FileSystemIndexHelper<>(this, fsrl);
super(fsrl, fsService);
}
/**
@ -86,7 +80,7 @@ public class SevenZipFileSystem implements GFileSystem {
ensurePasswords(monitor);
}
catch (SevenZipException e) {
throw new IOException("Failed to open archive: " + fsrl, e);
throw new IOException("Failed to open archive: " + fsFSRL, e);
}
}
@ -101,20 +95,19 @@ public class SevenZipFileSystem implements GFileSystem {
FSUtilities.uncheckedClose(szBPStream, null);
szBPStream = null;
fsIndexHelper.clear();
fsIndex.clear();
items = null;
}
private void indexFiles(TaskMonitor monitor) throws CancelledException, SevenZipException {
monitor.initialize(items.length);
monitor.setMessage("Indexing files");
monitor.initialize(items.length, "Indexing files");
for (ISimpleInArchiveItem item : items) {
if (monitor.isCancelled()) {
throw new CancelledException();
}
long itemSize = Objects.requireNonNullElse(item.getSize(), -1L);
fsIndexHelper.storeFile(fixupItemPath(item), item.getItemIndex(), item.isFolder(),
fsIndex.storeFile(fixupItemPath(item), item.getItemIndex(), item.isFolder(),
itemSize, item);
}
}
@ -124,7 +117,7 @@ public class SevenZipFileSystem implements GFileSystem {
if (items.length == 1 && itemPath.isBlank()) {
// special case when there is a single unnamed file.
// use the name of the 7zip file itself, minus the extension
itemPath = FilenameUtils.getBaseName(fsrl.getContainer().getName());
itemPath = FilenameUtils.getBaseName(fsFSRL.getContainer().getName());
}
if (itemPath.isEmpty()) {
itemPath = "<blank>";
@ -134,14 +127,15 @@ public class SevenZipFileSystem implements GFileSystem {
private String getPasswordForFile(GFile file, ISimpleInArchiveItem encryptedItem,
TaskMonitor monitor) {
FSRL containerFSRL = fsFSRL.getContainer();
int itemIndex = encryptedItem.getItemIndex();
if (!passwords.containsKey(itemIndex)) {
try (CryptoSession cryptoSession = fsService.newCryptoSession()) {
String prompt = passwords.isEmpty()
? fsrl.getContainer().getName()
: String.format("%s in %s", file.getName(), fsrl.getContainer().getName());
? containerFSRL.getName()
: "%s in %s".formatted(file.getName(), containerFSRL.getName());
for (Iterator<Password> pwIt =
cryptoSession.getPasswordsFor(fsrl.getContainer(), prompt); pwIt.hasNext();) {
cryptoSession.getPasswordsFor(containerFSRL, prompt); pwIt.hasNext();) {
try (Password passwordValue = pwIt.next()) {
monitor.setMessage("Testing password for " + file.getName());
@ -158,7 +152,7 @@ public class SevenZipFileSystem implements GFileSystem {
passwords.put(unlockedFileIndex, password);
}
if (!successFileIndexes.isEmpty()) {
cryptoSession.addSuccessfulPassword(fsrl.getContainer(), passwordValue);
cryptoSession.addSuccessfulPassword(containerFSRL, passwordValue);
}
if (passwords.containsKey(itemIndex)) {
break;
@ -190,7 +184,7 @@ public class SevenZipFileSystem implements GFileSystem {
return arrayResult;
}
private void ensurePasswords(TaskMonitor monitor) throws CancelledException, IOException {
private void ensurePasswords(TaskMonitor monitor) throws IOException {
// Alert! Unusual code!
// Background: contrary to normal expectations, zip container files can have a
// unique password per-embedded-file.
@ -210,7 +204,7 @@ public class SevenZipFileSystem implements GFileSystem {
ISimpleInArchiveItem encryptedItem = null;
while ((encryptedItem = getFirstItemWithoutPassword(encryptedItems)) != null &&
!monitor.isCancelled()) {
GFile gFile = fsIndexHelper.getFileByIndex(encryptedItem.getItemIndex());
GFile gFile = fsIndex.getFileByIndex(encryptedItem.getItemIndex());
if (gFile == null) {
throw new IOException("Unable to retrieve file " + encryptedItem.getPath());
}
@ -226,7 +220,7 @@ public class SevenZipFileSystem implements GFileSystem {
if (!noPasswordFoundList.isEmpty()) {
Msg.warn(this,
"Unable to find password for " + noPasswordFoundList.size() + " file(s) in " +
fsrl.getContainer().getName());
fsFSRL.getContainer().getName());
}
}
}
@ -252,45 +246,20 @@ public class SevenZipFileSystem implements GFileSystem {
return result;
}
@Override
public String getName() {
return fsrl.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsrl;
}
@Override
public boolean isClosed() {
return szBPStream == null;
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
@Override
public GFile lookup(String path) throws IOException {
return fsIndexHelper.lookup(path);
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsIndexHelper.getListing(directory);
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();
if (fsIndexHelper.getRootDir().equals(file)) {
if (fsIndex.getRootDir().equals(file)) {
result.add(NAME_ATTR, "/");
result.add("Archive Format", archiveFormat.toString());
}
else {
ISimpleInArchiveItem item = fsIndexHelper.getMetadata(file);
ISimpleInArchiveItem item = fsIndex.getMetadata(file);
if (item == null) {
return result;
}
@ -309,7 +278,7 @@ public class SevenZipFileSystem implements GFileSystem {
result.add(SIZE_ATTR, uncheckedGet(item::getSize, null));
Integer crc = uncheckedGet(item::getCRC, null);
result.add("CRC", crc != null ? String.format("%08X", crc) : null);
result.add("CRC", crc != null ? "%08X".formatted(crc) : null);
result.add("Compression Method", uncheckedGet(item::getMethod, null));
result.add(CREATE_DATE_ATTR, uncheckedGet(item::getCreationTime, null));
result.add(MODIFIED_DATE_ATTR, uncheckedGet(item::getLastWriteTime, null));
@ -321,7 +290,7 @@ public class SevenZipFileSystem implements GFileSystem {
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
try {
ISimpleInArchiveItem item = fsIndexHelper.getMetadata(file);
ISimpleInArchiveItem item = fsIndex.getMetadata(file);
if (item == null) {
return null;
@ -410,8 +379,8 @@ public class SevenZipFileSystem implements GFileSystem {
if (currentItem.isEncrypted() && !passwords.containsKey(currentIndex)) {
// if we lack a password for this item, don't try to extract it
Msg.debug(SevenZipFileSystem.this,
"No password for file[" + currentIndex + "] " + currentName + " of " +
fsrl.getContainer().getName() + ", unable to extract.");
"No password for file[%d] %s of %s, unable to extract.".formatted(currentIndex,
currentName, fsFSRL.getContainer().getName()));
return null;
}
@ -425,8 +394,7 @@ public class SevenZipFileSystem implements GFileSystem {
if (!currentItem.isFolder() && extractAskMode == ExtractAskMode.EXTRACT) {
try {
currentCacheEntryBuilder = fsService.createTempFile(currentItem.getSize());
monitor.initialize(currentItem.getSize());
monitor.setMessage("Extracting " + currentName);
monitor.initialize(currentItem.getSize(), "Extracting " + currentName);
}
catch (IOException e) {
throw new SevenZipException(e);
@ -441,9 +409,8 @@ public class SevenZipFileSystem implements GFileSystem {
String password = passwords.get(currentIndex);
if (password == null) {
Msg.debug(SevenZipFileSystem.this,
"No password for file[" + currentIndex + "] " + currentName + " of " +
fsrl.getContainer().getName());
Msg.debug(SevenZipFileSystem.this, "No password for file[%d] %s of %s"
.formatted(currentIndex, currentName, fsFSRL.getContainer().getName()));
// hack, return a non-null bad password. normally shouldn't get here as
// encrypted files w/missing password are skipped by getStream()
password = "";
@ -456,8 +423,8 @@ public class SevenZipFileSystem implements GFileSystem {
// STEP 3: SevenZip calls this multiple times for all the bytes in the file.
// We write them to our temp file.
if (currentCacheEntryBuilder == null) {
throw new SevenZipException(
"Bad Sevenzip Extract Callback state, " + currentIndex + ", " + currentName);
throw new SevenZipException("Bad Sevenzip Extract Callback state, %d, %s"
.formatted(currentIndex, currentName));
}
try {
currentCacheEntryBuilder.write(data);
@ -479,9 +446,9 @@ public class SevenZipFileSystem implements GFileSystem {
try {
FileCacheEntry fce = currentCacheEntryBuilder.finish();
if (extractOperationResult == ExtractOperationResult.OK) {
GFile gFile = fsIndexHelper.getFileByIndex(currentIndex);
GFile gFile = fsIndex.getFileByIndex(currentIndex);
if (gFile != null && gFile.getFSRL().getMD5() == null) {
fsIndexHelper.updateFSRL(gFile, gFile.getFSRL().withMD5(fce.getMD5()));
fsIndex.updateFSRL(gFile, gFile.getFSRL().withMD5(fce.getMD5()));
}
if (saveResults) {
extractResults.put(currentIndex, fce);
@ -490,8 +457,8 @@ public class SevenZipFileSystem implements GFileSystem {
FSUtilities.formatSize(fce.length()));
}
else {
Msg.warn(SevenZipFileSystem.this, "Failed to push file[" + currentIndex +
"] " + currentName + " to cache: " + extractOperationResult);
Msg.warn(SevenZipFileSystem.this, "Failed to push file[%d] %s to cache: %s"
.formatted(currentIndex, currentName, extractOperationResult));
extractOperationResultToException(extractOperationResult);
}
}

View File

@ -92,7 +92,7 @@ public class SparseImageFileSystem implements GFileSystem {
}
@Override
public GFile lookup(String path) throws IOException {
public GFile lookup(String path) {
return fsIndexHelper.lookup(path);
}

View File

@ -18,7 +18,6 @@ package ghidra.file.formats.tar;
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@ -38,23 +37,9 @@ import ghidra.util.task.TaskMonitor;
* <p>
*/
@FileSystemInfo(type = "tar", description = "TAR", priority = FileSystemInfo.PRIORITY_HIGH, factory = TarFileSystemFactory.class)
public class TarFileSystem implements GFileSystem {
public class TarFileSystem extends AbstractFileSystem<TarMetadata> {
private static class TarMetadata {
TarArchiveEntry tarArchiveEntry;
int fileNum;
TarMetadata(TarArchiveEntry tae, int fileNum) {
this.tarArchiveEntry = tae;
this.fileNum = fileNum;
}
}
private FSRLRoot fsrl;
private FileSystemService fsService;
private ByteProvider provider;
private FileSystemIndexHelper<TarMetadata> fsih;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private int fileCount;
/**
@ -65,10 +50,9 @@ public class TarFileSystem implements GFileSystem {
* @param fsService reference to the {@link FileSystemService}.
*/
public TarFileSystem(FSRLRoot fsrl, ByteProvider provider, FileSystemService fsService) {
this.fsrl = fsrl;
this.fsih = new FileSystemIndexHelper<>(this, fsrl);
super(fsrl, fsService);
this.provider = provider;
this.fsService = fsService;
}
ByteProvider getProvider() {
@ -84,31 +68,27 @@ public class TarFileSystem implements GFileSystem {
monitor.checkCancelled();
int fileNum = fileCount++;
GFile newFile = fsih.storeFile(tarEntry.getName(), fileCount,
GFile newFile = fsIndex.storeFile(tarEntry.getName(), fileCount,
tarEntry.isDirectory(), tarEntry.getSize(), new TarMetadata(tarEntry, fileNum));
if (tarEntry.getSize() < FileCache.MAX_INMEM_FILESIZE) {
// because tar files are sequential access, we cache smaller files if they
// will fit in a in-memory ByteProvider
try (ByteProvider bp =
fsService.getDerivedByteProvider(fsrl.getContainer(), newFile.getFSRL(),
fsService.getDerivedByteProvider(fsFSRL.getContainer(), newFile.getFSRL(),
newFile.getPath(), tarEntry.getSize(), () -> tarInput, monitor)) {
fsih.updateFSRL(newFile, newFile.getFSRL().withMD5(bp.getFSRL().getMD5()));
fsIndex.updateFSRL(newFile,
newFile.getFSRL().withMD5(bp.getFSRL().getMD5()));
}
}
}
}
}
@Override
public String getName() {
return fsrl.getContainer().getName();
}
@Override
public void close() throws IOException {
refManager.onClose();
fsih.clear();
fsIndex.clear();
if (provider != null) {
provider.close();
provider = null;
@ -120,19 +100,9 @@ public class TarFileSystem implements GFileSystem {
return provider == null;
}
@Override
public FSRLRoot getFSRL() {
return fsrl;
}
@Override
public int getFileCount() {
return fileCount;
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
TarMetadata tmd = fsih.getMetadata(file);
TarMetadata tmd = fsIndex.getMetadata(file);
if (tmd == null) {
return null;
}
@ -162,16 +132,11 @@ public class TarFileSystem implements GFileSystem {
return FileType.UNKNOWN;
}
@Override
public GFile lookup(String path) throws IOException {
return fsih.lookup(path);
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
TarMetadata tmd = fsih.getMetadata(file);
TarMetadata tmd = fsIndex.getMetadata(file);
if (tmd == null) {
throw new IOException("Unknown file " + file);
}
@ -197,14 +162,5 @@ public class TarFileSystem implements GFileSystem {
return fileBP;
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsih.getListing(directory);
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
}

View File

@ -0,0 +1,28 @@
/* ###
* 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.file.formats.tar;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
class TarMetadata {
TarArchiveEntry tarArchiveEntry;
int fileNum;
TarMetadata(TarArchiveEntry tae, int fileNum) {
this.tarArchiveEntry = tae;
this.fileNum = fileNum;
}
}

View File

@ -15,13 +15,11 @@
*/
package ghidra.file.formats.zip;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.io.*;
import java.sql.Date;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.*;
@ -48,24 +46,13 @@ import ghidra.util.task.TaskMonitor;
* and {@link GFileSystem#getDescription()} to operate in the default manner.
*/
@FileSystemInfo(type = "zip", description = "ZIP", factory = ZipFileSystemFactory.class, priority = FileSystemInfo.PRIORITY_HIGH)
public class ZipFileSystemBuiltin implements GFileSystem {
public class ZipFileSystemBuiltin extends AbstractFileSystem<ZipEntry> {
static final String TEMPFILE_PREFIX = "ghidra_tmp_zipfile";
private FileSystemIndexHelper<ZipEntry> fsIndexHelper;
private FSRLRoot fsFSRL;
private ZipFile zipFile;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private FileSystemService fsService;
public ZipFileSystemBuiltin(FSRLRoot fsFSRL, FileSystemService fsService) {
this.fsFSRL = fsFSRL;
this.fsService = fsService;
this.fsIndexHelper = new FileSystemIndexHelper<>(this, fsFSRL);
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
super(fsFSRL, fsService);
}
@Override
@ -75,7 +62,7 @@ public class ZipFileSystemBuiltin implements GFileSystem {
zipFile.close();
zipFile = null;
}
fsIndexHelper.clear();
fsIndex.clear();
}
@Override
@ -83,16 +70,6 @@ public class ZipFileSystemBuiltin implements GFileSystem {
return zipFile == null;
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public int getFileCount() {
return fsIndexHelper.getFileCount();
}
public void mount(File f, boolean deleteFileWhenDone, TaskMonitor monitor)
throws CancelledException, IOException {
@ -109,14 +86,14 @@ public class ZipFileSystemBuiltin implements GFileSystem {
while (entries.hasMoreElements()) {
monitor.checkCancelled();
ZipEntry currentEntry = entries.nextElement();
fsIndexHelper.storeFile(currentEntry.getName(), -1, currentEntry.isDirectory(),
fsIndex.storeFile(currentEntry.getName(), -1, currentEntry.isDirectory(),
currentEntry.getSize(), currentEntry);
}
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
ZipEntry zipEntry = fsIndexHelper.getMetadata(file);
ZipEntry zipEntry = fsIndex.getMetadata(file);
if (zipEntry == null) {
return null;
}
@ -137,22 +114,17 @@ public class ZipFileSystemBuiltin implements GFileSystem {
return "ZipFilesystemBuiltin [ fsrl=" + fsFSRL + ", filename=" + zipFile.getName() + " ]";
}
@Override
public GFile lookup(String path) throws IOException {
return fsIndexHelper.lookup(path);
}
@Override
public InputStream getInputStream(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
ZipEntry zipEntry = fsIndexHelper.getMetadata(file);
ZipEntry zipEntry = fsIndex.getMetadata(file);
return (zipEntry != null) ? zipFile.getInputStream(zipEntry) : null;
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
ZipEntry zipEntry = fsIndexHelper.getMetadata(file);
ZipEntry zipEntry = fsIndex.getMetadata(file);
if (zipEntry == null) {
return null;
}
@ -164,14 +136,4 @@ public class ZipFileSystemBuiltin implements GFileSystem {
() -> zipFile.getInputStream(zipEntry),
monitor);
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsIndexHelper.getListing(directory);
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
}