diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsAnalyzer.java
new file mode 100644
index 0000000000..4f34fc60ab
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsAnalyzer.java
@@ -0,0 +1,117 @@
+/* ###
+ * 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.cramfs;
+
+import ghidra.app.services.AbstractAnalyzer;
+import ghidra.app.services.AnalyzerType;
+import ghidra.app.util.bin.*;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.app.util.opinion.BinaryLoader;
+import ghidra.framework.options.Options;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSetView;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.lang.Language;
+import ghidra.program.model.lang.Processor;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+public class CramFsAnalyzer extends AbstractAnalyzer {
+ // Seen offsets plus the count of how many times seen. Should only be 1
+ // for each file inode, if 2 inodes share data space and have same contents.
+
+ public CramFsAnalyzer() {
+ super("CramFS Analyzer", "Annotates CramFS binaries", AnalyzerType.BYTE_ANALYZER);
+ }
+
+ @Override
+ public boolean getDefaultEnablement(Program program) {
+ return true;
+ }
+
+ @Override
+ public boolean canAnalyze(Program program) {
+ try {
+ Options options = program.getOptions(Program.PROGRAM_INFO);
+ String format = options.getString("Executable Format", null);
+ if (!BinaryLoader.BINARY_NAME.equals(format)) {
+ return false;
+ }
+ Language language = program.getLanguage();
+ if (language.getProcessor() == Processor.findOrPossiblyCreateProcessor("DATA") &&
+ !language.isBigEndian()) {
+ return false;
+ }
+ int magic = program.getMemory().getInt(program.getMinAddress());
+ return magic == CramFsConstants.MAGIC;
+ }
+ catch (MemoryAccessException e) {
+ //Ignore
+ }
+ return false;
+ }
+
+ @Override
+ public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
+ throws CancelledException {
+ Address minAddress = program.getMinAddress();
+ boolean isLE = !program.getLanguage().isBigEndian();
+
+ try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), minAddress)) {
+ BinaryReader reader = new BinaryReader(provider, isLE);
+ CramFsSuper cramFsSuper = new CramFsSuper(reader);
+ DataType dataType = cramFsSuper.toDataType();
+ program.getListing().createData(minAddress, dataType);
+ program.getListing()
+ .setComment(minAddress, CodeUnit.PLATE_COMMENT,
+ cramFsSuper.getRoot().toString());
+ int offset = cramFsSuper.getRoot().getOffsetAdjusted();
+
+ for (int i = 0; i < cramFsSuper.getFsid().getFiles() - 1; i++) {
+
+ monitor.checkCancelled();
+ reader.setPointerIndex(offset);
+ Address inodeAddress = minAddress.add(offset);
+ CramFsInode newInode = new CramFsInode(reader);
+
+ if (newInode.isFile()) {
+ Address inodeDataAddress = minAddress.add(newInode.getOffsetAdjusted());
+ program.getListing()
+ .setComment(inodeDataAddress, CodeUnit.PLATE_COMMENT,
+ newInode.getName() + " Data/Bytes\n");
+ }
+
+ DataType inodeDataType = newInode.toDataType();
+ program.getListing().createData(inodeAddress, inodeDataType);
+
+ program.getListing()
+ .setComment(inodeAddress, CodeUnit.PLATE_COMMENT,
+ newInode.getName() + "\n" + newInode.toString());
+
+ offset += inodeDataType.getLength();
+ }
+ }
+ catch (Exception e) {
+ log.appendException(e);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlock.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlock.java
new file mode 100644
index 0000000000..613df897ed
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlock.java
@@ -0,0 +1,92 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.ByteProvider;
+
+/**
+ * @see /fs/cramfs
+ */
+public class CramFsBlock {
+ private int blockPointer;
+ private int startAddress;
+ private boolean isDirectPointer;
+ private boolean isCompressed;
+ private int blockSize;
+ private ByteProvider provider;
+
+ static final int IS_DIRECT_POINTER = (1 << 30);
+ static final int IS_UNCOMPRESSED = (1 << 31);
+
+ /**
+ * This constructor is for regular contiguous blocks in a cramfs file
+ * that do not have the extension flag set.
+ * @param start the address for the start of this block.
+ * @param blockSize the size of the cramfs block.
+ * @param provider the byteProvider for the block header.
+ */
+ public CramFsBlock(int start, int blockSize, ByteProvider provider) {
+ startAddress = blockPointer = start;
+ this.blockSize = blockSize;
+ this.provider = provider;
+ isDirectPointer = false;
+ isCompressed = false;
+ }
+
+ /**
+ * Returns the block pointer for the cramfs block.
+ * @return the block pointer for the cramfs block.
+ */
+ public int getBlockPointer() {
+ return blockPointer;
+ }
+
+ /**
+ * Returns true if the block is a direct pointer.
+ * @return true if the block is a direct pointer.
+ */
+ public boolean isDirectPointer() {
+ return isDirectPointer;
+ }
+
+ /**
+ * Returns true if the block is compressed.
+ * @return true if the block is compressed.
+ */
+ public boolean isCompressed() {
+ return isCompressed;
+ }
+
+ /**
+ * Returns the size of the cramfs block.
+ * @return the size of the cramfs block.
+ */
+ public int getBlockSize() {
+ return blockSize;
+ }
+
+ /**
+ * Reads the data block in its entirety.
+ * @return the read bytes in a byte array.
+ * @throws IOException if there is an error while reading the data block.
+ */
+ public byte[] readBlock() throws IOException {
+ return provider.readBytes(startAddress, blockSize);
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlockFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlockFactory.java
new file mode 100644
index 0000000000..a6acdbf885
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlockFactory.java
@@ -0,0 +1,94 @@
+/* ###
+ * 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.cramfs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.util.bin.ByteProvider;
+
+public class CramFsBlockFactory {
+ static final int IS_DIRECT_POINTER = (1 << 30);
+ static final int IS_UNCOMPRESSED = (1 << 31);
+
+ private CramFsInode cramfsInode;
+ private ByteProvider provider;
+ private List blockPointerList;
+ //For the size of compressed block sizes.
+ private List blockSizes;
+ //It is possible this will always default to false, but just in case.
+ //This will determine if the data blocks have special conditions on them.
+ private boolean blockPointerExtensionsEnabled;
+
+ /**
+ * This class takes an iNode and produces a List of CramFsBlocks that are
+ * set appropriately depending on their flags,
+ * and the flag CRAMFS_FLAG_EXT_BLOCK_POINTERS from the CramFsSuper block.
+ * @param cramfsInode the parent node for this block.
+ * @param provider the byteProvider for the block header.
+ * @param blockPointerList a list of the block pointers.
+ * @param blockPointerExtensionsEnabled true if the block pointer extensions are enabled.
+ */
+ public CramFsBlockFactory(CramFsInode cramfsInode, ByteProvider provider,
+ List blockPointerList, boolean blockPointerExtensionsEnabled) {
+ this.cramfsInode = cramfsInode;
+ this.provider = provider;
+ this.blockPointerExtensionsEnabled = blockPointerExtensionsEnabled;
+ this.blockPointerList = blockPointerList;
+ }
+
+ /**
+ * This function will use the inode to calculate certain things for the block,
+ * such as calculating compressed block sizes etc.
+ * If the block pointer extension flag is set not in the super block,
+ * we will calculate the size of each zlibbed block, and create a list of blocks appropriately.
+ * @return the block list.
+ */
+ public List produceBlocks() {
+
+ List blockList = new ArrayList<>();
+
+ if (!blockPointerExtensionsEnabled) { //focus on this one
+ blockSizes = calculateCompressedBlockSizes();
+ //Use blockSizes to create Blocks.
+ for (int i = 0; i < blockSizes.size(); i++) {
+ blockList.add(new CramFsBlock(blockPointerList.get(i), blockSizes.get(i).intValue(),
+ provider));
+ }
+ }
+
+ return blockList;
+ }
+
+ /**
+ * Returns the cramfsInode.
+ * @return the cramfsInode.
+ */
+ public CramFsInode getCramfsInode() {
+ return cramfsInode;
+ }
+
+ private List calculateCompressedBlockSizes() {
+ List compressedBlockSizes = new ArrayList();
+
+ for (int i = 0; i < blockPointerList.size() - 1; i++) {
+ compressedBlockSizes.add(blockPointerList.get(i + 1) - blockPointerList.get(i));
+ }
+
+ return compressedBlockSizes;
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlockReader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlockReader.java
new file mode 100644
index 0000000000..bd64b12677
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsBlockReader.java
@@ -0,0 +1,177 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.file.formats.zlib.ZLIB;
+
+public class CramFsBlockReader {
+
+ private ByteProvider provider;
+ private CramFsInode cramfsInode;
+ private List blockPointerTable = new LinkedList<>();
+ private List compressedBlockSizes = new ArrayList<>();
+ private boolean isLittleEndian;
+
+ /**
+ * This constructor reads the CramFS Block.
+ * @param provider the byteProvider for the block header.
+ * @param cramfsInode the parent node for this block.
+ * @param isLittleEndian if the block is little endian or not.
+ * @throws IOException if there is an error while reading the block.
+ */
+ public CramFsBlockReader(ByteProvider provider, CramFsInode cramfsInode, boolean isLittleEndian)
+ throws IOException {
+ this.provider = provider;
+ this.cramfsInode = cramfsInode;
+ this.isLittleEndian = isLittleEndian;
+ populateBlockPointerTable();
+ calculateCompressedBlockSizes();
+ }
+
+ private void populateBlockPointerTable() throws IOException {
+
+ int numBlockPointers = calculateStartAddress();
+
+ if (numBlockPointers < 0) {
+ throw new IOException("Start Address for data block not found");
+ }
+
+ int inodeDataOffset = cramfsInode.getOffsetAdjusted();
+ for (int i = 0; i < numBlockPointers - 1; i++) {
+
+ byte[] tempBuffer =
+ provider.readBytes(inodeDataOffset, CramFsConstants.BLOCK_POINTER_SIZE);
+ //byteProvider will be Big Endian by default
+ ByteBuffer byteBuffer = ByteBuffer.wrap(tempBuffer);
+ if (isLittleEndian) {
+ blockPointerTable.add(Integer.reverseBytes(byteBuffer.getInt()));
+ }
+ else {
+ blockPointerTable.add(byteBuffer.getInt());
+ }
+
+ inodeDataOffset += CramFsConstants.BLOCK_POINTER_SIZE;
+ }
+ }
+
+ /**
+ * Calculates the start address of the data block using the
+ * block pointer table that precedes compressed data.
+ * @return the number of block pointers associated with this data section.
+ * @throws IOException if error occurs reading from the byte provider.
+ */
+ private int calculateStartAddress() throws IOException {
+ int numBlockPointers = -1;
+ int dataOffset = cramfsInode.getOffsetAdjusted();
+ int dataOffsetStart = dataOffset;
+ boolean firstAddressFound = false;
+
+ while (!firstAddressFound) {
+ byte[] possibleZlibHeader =
+ provider.readBytes(dataOffset, CramFsConstants.ZLIB_MAGIC_SIZE);
+ if (Arrays.equals(possibleZlibHeader, ZLIB.ZLIB_COMPRESSION_DEFAULT) ||
+ Arrays.equals(possibleZlibHeader, ZLIB.ZLIB_COMPRESSION_BEST) ||
+ Arrays.equals(possibleZlibHeader, ZLIB.ZLIB_COMPRESSION_NO_LOW)) {
+ blockPointerTable.add(dataOffset);
+ firstAddressFound = true;
+ return ((dataOffset - dataOffsetStart) / CramFsConstants.BLOCK_POINTER_SIZE) + 1;
+ }
+ dataOffset += 4;
+ }
+ return numBlockPointers;
+ }
+
+ /**
+ * Uses the block pointer table which contains addresses
+ * to calculate the size of the compressed blocks of data
+ * for use in uncompressing each block. Adds the size of each block
+ * to the compressedBlockSizes arrayList.
+ */
+ private void calculateCompressedBlockSizes() {
+
+ for (int i = 0; i < blockPointerTable.size() - 1; i++) {
+ compressedBlockSizes.add(blockPointerTable.get(i + 1) - blockPointerTable.get(i));
+ }
+
+ }
+
+ /**
+ * Reads one block from the data pointed to by the CramfsInode.
+ * @param dataBlockIndex the index of the block to read.
+ * @return a byte array representing the compressed data for a compressed block.
+ * @throws IOException if error occurs when reading the data block.
+ */
+ public byte[] readDataBlock(int dataBlockIndex) throws IOException {
+ return provider.readBytes(blockPointerTable.get(dataBlockIndex),
+ compressedBlockSizes.get(dataBlockIndex));
+ }
+
+ /**
+ * Sends compressed data block to be uncompressed.
+ * @param dataBlockIndex the index of the block to read.
+ * @return Uncompressed data as a ByteArrayInputStream.
+ * @throws IOException if an error occurs when reading the decompressed data block.
+ */
+ public InputStream readDataBlockDecompressed(int dataBlockIndex) throws IOException {
+ Integer index = blockPointerTable.get(dataBlockIndex);
+ Integer length = compressedBlockSizes.get(dataBlockIndex);
+ byte[] compressedBytes = provider.readBytes(index, length);
+ InputStream compressedInputStream = new ByteArrayInputStream(compressedBytes);
+ ZLIB zlib = new ZLIB();
+
+ ByteArrayOutputStream decompressedOutputStream =
+ zlib.decompress(compressedInputStream, CramFsConstants.DEFAULT_BLOCK_SIZE);
+ return new ByteArrayInputStream(decompressedOutputStream.toByteArray());
+ }
+
+ /**
+ * Gets the provider.
+ * @return provider.
+ */
+ public ByteProvider getProvider() {
+ return provider;
+ }
+
+ /**
+ * Gets the CramfsInode.
+ * @return cramfsInode.
+ */
+ public CramFsInode getCramfsInode() {
+ return cramfsInode;
+ }
+
+ /**
+ * Gets the block pointer table.
+ * @return the block pointer table.
+ */
+ public List getBlockPointerTable() {
+ return blockPointerTable;
+ }
+
+ /**
+ * Gets the number of block pointers.
+ * @return the number of block pointers.
+ */
+ public int getNumBlockPointers() {
+ return blockPointerTable.size() - 1;
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsConstants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsConstants.java
new file mode 100644
index 0000000000..17dec5dd33
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsConstants.java
@@ -0,0 +1,53 @@
+/* ###
+ * 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.cramfs;
+
+/**
+* @see /fs/cramfs
+*/
+public final class CramFsConstants {
+ public static final int HEADER_STRING_LENGTH = 16;
+ public static final int MAGIC = 0x28cd3d45;
+
+ /**
+ * Constant size of an inode in bytes in memory.
+ */
+ public static final int INODE_SIZE = 12;
+
+ /**
+ * Flag as described in cramfs_fs.h.
+ */
+ public static final int CRAMFS_FLAG_EXT_BLOCK_POINTERS = 0x00000800;
+
+ /**
+ * Documentation points to this being the default size
+ * provide option for user if they know the block size.
+ */
+ public static final int DEFAULT_BLOCK_SIZE = 4096;
+ public static final int BLOCK_POINTER_SIZE = 4;
+ public static final int ZLIB_MAGIC_SIZE = 2;
+
+ /**
+ * Width of various bitfields in struct {@link CramFsInode}
+ */
+ public static final int CRAMFS_MODE_WIDTH = 16;
+ public static final int CRAMFS_UID_WIDTH = 16;
+ public static final int CRAMFS_SIZE_WIDTH = 24;
+ public static final int CRAMFS_GID_WIDTH = 8;
+ public static final int CRAMFS_NAMELEN_WIDTH = 6;
+ public static final int CRAMFS_OFFSET_WIDTH = 26;
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsFileSystem.java
new file mode 100644
index 0000000000..f2ad1e129c
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsFileSystem.java
@@ -0,0 +1,201 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.IOException;
+import java.util.*;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.formats.gfilesystem.*;
+import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
+import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
+import ghidra.util.*;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.CryptoException;
+import ghidra.util.task.TaskMonitor;
+
+@FileSystemInfo(type = "cramfs", description = "CRAMFS", factory = GFileSystemBaseFactory.class)
+public class CramFsFileSystem extends GFileSystemBase {
+
+ private boolean isLittleEndian;
+ private CramFsSuper cramFsSuper;
+
+ private Map fileToInodeMap = new HashMap<>();
+
+ private List rootListing = new ArrayList<>();
+
+ private Map> directoryToChildMap = new HashMap<>();
+
+ public CramFsFileSystem(String fileSystemName, ByteProvider provider) {
+ super(fileSystemName, provider);
+ }
+
+ @Override
+ public boolean isValid(TaskMonitor monitor) throws IOException {
+ byte[] bytes = provider.readBytes(0, 4);
+ DataConverter[] dataConverter =
+ new DataConverter[] { new LittleEndianDataConverter(), new BigEndianDataConverter() };
+
+ for (int i = 0; i < dataConverter.length; i++) {
+ if (dataConverter[i].getInt(bytes) == CramFsConstants.MAGIC) {
+ isLittleEndian = !dataConverter[i].isBigEndian();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
+ BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+ cramFsSuper = new CramFsSuper(reader);
+ if (cramFsSuper.isExtensionsBlockPointerFlagEnabled()) {
+ throw new IOException("Extended Block Pointer flag is set, currently unsupported");
+ }
+
+ List childList = cramFsSuper.getChildList();
+ Map subDirectoryMap = new HashMap<>();
+
+ GFile parent = root;
+
+ for (CramFsInode cramFsInode : childList) {
+ monitor.checkCancelled();
+
+ if (cramFsInode.isDirectory()) {
+ subDirectoryMap.put((long) cramFsInode.getOffsetAdjusted(), cramFsInode);
+ }
+
+ if (subDirectoryMap.containsKey(cramFsInode.getAddress())) {
+ break;
+ }
+
+ GFileImpl iNodeFile = GFileImpl.fromPathString(this, parent, cramFsInode.getName(),
+ null, cramFsInode.isDirectory(), cramFsInode.getSize());
+
+ fileToInodeMap.put(iNodeFile, cramFsInode);
+
+ rootListing.add(iNodeFile);
+ }
+ }
+
+ /**
+ * Small utility to search childList for specific cramFsInode by name.
+ * @param targetName the name of the cramFsInode we are searching for.
+ * @return the target cramFs Inode.
+ */
+ private CramFsInode getChildInodeByName(String targetName) {
+ CramFsInode target = null;
+ List childList = cramFsSuper.getChildList();
+
+ for (CramFsInode cramFsInode : childList) {
+ if (cramFsInode.getName().contentEquals(targetName)) {
+ target = cramFsInode;
+ break;
+ }
+ }
+ return target;
+ }
+
+ @Override
+ public List getListing(GFile directory) throws IOException {
+
+ //Return listing for root directory, should be first listing returned anyway.
+ //Checking for null fixed some issues as well.
+ if (directory == root || directory == null) {
+ return rootListing;
+ }
+
+ if (directoryToChildMap.containsKey(directory)) {
+ return directoryToChildMap.get(directory);
+ }
+
+ CramFsInode parentInode = getChildInodeByName(directory.getName());
+ //Store current inode and size info in a counter.
+ int directoryLength = parentInode.getSize();
+
+ List directoryListing = populateChildList(directory, parentInode, directoryLength);
+
+ directoryToChildMap.put(directory, directoryListing);
+ return directoryListing;
+ }
+
+ private List populateChildList(GFile directory, CramFsInode parentInode,
+ int directoryLength) {
+
+ List childList = cramFsSuper.getChildList();
+
+ List directoryListing = new ArrayList<>();
+
+ int startIndex = computeStartIndex(childList, parentInode);
+ for (int i = startIndex; i < childList.size(); i++) {
+ if (directoryLength <= 0) {
+ break;
+ }
+
+ CramFsInode entryInode = childList.get(i);
+ GFileImpl iNodeFile = GFileImpl.fromPathString(this, directory, entryInode.getName(),
+ null, entryInode.isDirectory(), entryInode.getSize());
+
+ directoryListing.add(iNodeFile);
+ directoryLength -= (CramFsConstants.INODE_SIZE + (entryInode.getNamelen() * 4));
+ fileToInodeMap.put(iNodeFile, entryInode);
+ }
+
+ return directoryListing;
+ }
+
+ /**
+ * Used to find the first entry in a directory from the list of child inodes.
+ * @param childList the list of child inodes.
+ * @param parentInode the parent cramFsInode.
+ * @return the start index in a directory.
+ */
+ private int computeStartIndex(List childList, CramFsInode parentInode) {
+
+ int startIndex = 0;
+
+ //Iterate through full childlist until an inode address matches the directory offset.
+ for (int i = 0; i < childList.size(); i++) {
+ if (childList.get(i).getAddress() == parentInode.getOffsetAdjusted()) {
+ startIndex = i;
+ break;
+ }
+ }
+ return startIndex;
+ }
+
+ @Override
+ public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
+ throws IOException, CancelledException {
+
+ CramFsInode childInode = fileToInodeMap.get(file);
+
+ if (childInode.getSize() >= 0xffffff) {
+ throw new IOException("File is larger than 16MB and was clipped, cannot open.");
+ }
+
+ ByteProvider fileBP = fsService.getDerivedByteProvider(provider.getFSRL(), file.getFSRL(),
+ file.getPath(), childInode.getSize(), () -> {
+ return new LazyCramFsInputStream(provider, childInode,
+ cramFsSuper.isLittleEndian());
+ }, monitor);
+
+ return fileBP;
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInfo.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInfo.java
new file mode 100644
index 0000000000..25525e3eb6
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInfo.java
@@ -0,0 +1,86 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.data.*;
+import ghidra.util.exception.DuplicateNameException;
+
+public class CramFsInfo implements StructConverter {
+
+ private int crc;
+ private int edition;
+ private int blocks;
+ private int files;
+
+ /**
+ * This constructor reads the cramfs info/attributes
+ * @param reader the binary reader for the cramfs info/attributes.
+ * @throws IOException if there is an error while reading the cramfs info/attributes.
+ */
+ public CramFsInfo(BinaryReader reader) throws IOException {
+ crc = reader.readNextInt();
+ edition = reader.readNextInt();
+ blocks = reader.readNextInt();
+ files = reader.readNextInt();
+ }
+
+ /**
+ * Returns the crc value of the cramfs info.
+ * @return the crc value of the cramfs info.
+ */
+ public int getCrc() {
+ return crc;
+ }
+
+ /**
+ * Returns the edition of the cramfs info.
+ * @return the edition of the cramfs info.
+ */
+ public int getEdition() {
+ return edition;
+ }
+
+ /**
+ * Returns the blocks of the cramfs info.
+ * @return the blocks of the cramfs info.
+ */
+ public int getBlocks() {
+ return blocks;
+ }
+
+ /**
+ * Returns the files of the cramfs info.
+ * @return the files of the cramfs info.
+ */
+ public int getFiles() {
+ return files;
+ }
+
+ @Override
+ public DataType toDataType() throws DuplicateNameException, IOException {
+ Structure struct = new StructureDataType("cramfs_info", 0);
+ struct.add(DWORD, "crc", null);
+ struct.add(DWORD, "edition", null);
+ struct.add(DWORD, "blocks", null);
+ struct.add(DWORD, "files", null);
+ return struct;
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInode.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInode.java
new file mode 100644
index 0000000000..6a500089e7
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInode.java
@@ -0,0 +1,197 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.data.*;
+import ghidra.util.exception.DuplicateNameException;
+
+public class CramFsInode implements StructConverter {
+
+ //Packed integers, one 32 bit integer, but two less than 32 bit integers packed into the bit space.
+ //__u32 mode: CRAMFS_MODE_WIDTH, id:CRAMFS_UID_WIDTH
+ //__u32 size:CRAMFS_SIZE_WIDTH, gid:CRAMFS_GID_WIDTH
+ //__u32 namelen:CRAMFS_NAMELEN_WIDTH, offset:CRAMFS_OFFSET_WIDTH
+ private int mode;
+ private int uid;
+ private int size; //Must be a byte array of 3 bytes for 24 bits
+ private int gid; //8 bit sized integer
+ private int namelen;
+ private int offset;
+ private String name; //Not explicitly in cramfs_inode
+ private long address; //absolute address in file, used for directory traversal.
+
+ public CramFsInode(BinaryReader reader) throws IOException {
+ //Before reader reads anything and progresses, get addr for start of inode.
+ address = reader.getPointerIndex();
+ int modeUID = reader.readNextInt();
+ int sizeGID = reader.readNextInt();
+ int namelenOffset = reader.readNextInt();
+
+ if (reader.isBigEndian()) {
+ modeUID = Integer.reverseBytes(modeUID);
+ sizeGID = Integer.reverseBytes(sizeGID);
+ namelenOffset = Integer.reverseBytes(namelenOffset);
+ }
+
+ //Always read value as little endian
+ uid = ((modeUID & 0xffff0000) >> CramFsConstants.CRAMFS_UID_WIDTH) & 0x0000ffff;
+ mode = (modeUID & 0x0000ffff);
+
+ gid = ((sizeGID & 0xff000000) >> CramFsConstants.CRAMFS_SIZE_WIDTH) & 0x000000ff;
+ size = (sizeGID & 0x00ffffff);
+
+ offset =
+ ((namelenOffset & 0xffffffc0) >> CramFsConstants.CRAMFS_NAMELEN_WIDTH) & 0x0cffffff;
+ namelen = (namelenOffset & 0x0000003f);
+
+ name = reader.readNextAsciiString(namelen * 4);
+ }
+
+ @Override
+ public DataType toDataType() throws DuplicateNameException, IOException {
+ int length = namelen * 4;
+
+ Structure struct = new StructureDataType("cramfs_inode_" + length, 0);
+ struct.add(DWORD, "modeUID", null);
+ struct.add(DWORD, "sizeGID", null);
+ struct.add(DWORD, "namelenOffset", null);
+
+ if (namelen > 0) {
+ struct.add(STRING, length, "name", null);
+ }
+ return struct;
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("mode = 0x" + Integer.toHexString(mode) + " 16 MSB, UID = 0x" +
+ Integer.toHexString(uid) + " 16 LSB\n");
+ buffer.append("size = 0x" + Integer.toHexString(size) + " 24 MSB, GID = 0x" +
+ Integer.toHexString(gid) + " 8 LSB\n");
+ buffer.append("namelen = 0x" + Integer.toHexString(namelen) + " 6 MSB, offset = 0x" +
+ Integer.toHexString(offset) + " 26 LSB\n");
+
+ if (isFile()) {
+ buffer.append("Pointer to data = 0x" + Integer.toHexString(getOffsetAdjusted()) + "\n");
+ }
+
+ if (isDirectory()) {
+ if (offset == 0) {
+ buffer.append("EMPTY DIRECTORY\n");
+ }
+ else {
+ buffer.append(
+ "Pointer to next inode = 0x" + Integer.toHexString(getOffsetAdjusted()) + "\n");
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the mode of the CramFSInode.
+ * @return the mode.
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Returns the unique identifier of the inode.
+ * @return the unique identifier.
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * Returns the size of the inode.
+ * @return the size.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the group identifier of the inode.
+ * @return the group identifier.
+ */
+ public int getGid() {
+ return gid;
+ }
+
+ /**
+ * Returns the name length of the inode.
+ * @return the name length.
+ */
+ public int getNamelen() {
+ return namelen;
+ }
+
+ /**
+ * Returns the offset of the inode.
+ * @return the offset.
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
+ * Returns the name of the inode.
+ * @return the name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the adjusted offset of the inode.
+ * @return the adjusted offset of the inode.
+ */
+ public int getOffsetAdjusted() {
+ return offset * 4;
+ }
+
+ /**
+ * Returns true if the inode is a file.
+ * @return true if the inode is a file.
+ */
+ public boolean isFile() {
+ return ((mode & 0x8000) != 0);
+ }
+
+ /**
+ * Returns true if the inode is a directory.
+ * @return true if the inode is a directory.
+ */
+ public boolean isDirectory() {
+ return ((mode & 0x4000) != 0);
+ }
+
+ /**
+ * Returns the address of the inode.
+ * @return the address of the inode.
+ */
+ public long getAddress() {
+ return address;
+ }
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInputStream.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInputStream.java
new file mode 100644
index 0000000000..632147fd47
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsInputStream.java
@@ -0,0 +1,127 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.file.formats.zlib.ZLIB;
+
+public class CramFsInputStream extends InputStream {
+ private CramFsInode iNode;
+ private List blockPointerList = new ArrayList<>();
+ private List blockList;
+ private List decompressedBlockStreams = new ArrayList<>();
+ private List decompressedOutputList = new ArrayList<>();
+ private ZLIB zlib = new ZLIB();
+ private ByteProvider byteProvider;
+ private CramFsBlockReader cramfsBlockReader;
+ private int defaultBlockSize;
+ private int currentByte = 0;
+ private boolean blockExtensionEnabled;
+
+ /**
+ * Constructor for cramfs input stream.
+ * @param byteProvider the underlined byte provider for the input stream.
+ * @param iNode the parent node for the input stream.
+ * @param blockExtensionEnabled the enabled block extensions for the input stream.
+ * @throws IOException if there is an error when creating the input stream.
+ */
+ public CramFsInputStream(ByteProvider byteProvider, CramFsInode iNode,
+ boolean blockExtensionEnabled) throws IOException {
+ this.iNode = iNode;
+ this.byteProvider = byteProvider;
+ this.blockExtensionEnabled = blockExtensionEnabled;
+ defaultBlockSize = CramFsConstants.DEFAULT_BLOCK_SIZE;
+ blockList = getDataBlocks();
+ decompressAllBlocks();
+ combineDecompressedBlockStreams();
+ }
+
+ /**
+ * Sends the inode to the CramFs block factory and gets back a list of
+ * CramFs blocks for the data associated with the inode.
+ * @return a list of cramFs blocks to be used for decompression.
+ */
+ private List getDataBlocks() {
+ CramFsBlockFactory blockFactory =
+ new CramFsBlockFactory(iNode, byteProvider, blockPointerList, blockExtensionEnabled);
+ return blockFactory.produceBlocks();
+ }
+
+ /**
+ * Gets the Cram file system block list.
+ * @return the block list
+ */
+ public List getBlockList() {
+ return blockList;
+ }
+
+ /**
+ * Decompress all the data blocks that an inode points to.
+ * Adds the uncompressed blocks to an internal list for later processing.
+ * @throws IOException if there is an error when decompressing the data blocks.
+ */
+ private void decompressAllBlocks() throws IOException {
+ for (int i = 0; i < cramfsBlockReader.getNumBlockPointers() - 1; i++) {
+ InputStream compressedIn = new ByteArrayInputStream(cramfsBlockReader.readDataBlock(i));
+ decompressedBlockStreams
+ .add(zlib.decompress(compressedIn, CramFsConstants.DEFAULT_BLOCK_SIZE));
+ }
+ }
+
+ /**
+ * Combines all the ZLIB decompressed block stream bytes into one list.
+ */
+ private void combineDecompressedBlockStreams() {
+ for (int i = 0; i < decompressedBlockStreams.size(); i++) {
+ byte[] bytes = decompressedBlockStreams.get(i).toByteArray();
+ List bytesList = Arrays.asList(ArrayUtils.toObject(bytes));
+ decompressedOutputList.addAll(bytesList);
+ }
+ }
+
+ /**
+ * Decompress the specified block.
+ * @param blockIndex the block to decompress.
+ * @return decompressed output stream.
+ * @throws IOException if zlib decompress fails.
+ */
+ public ByteArrayOutputStream decompressBlock(int blockIndex) throws IOException {
+ InputStream compressedIn = new ByteArrayInputStream(blockList.get(blockIndex).readBlock());
+ return zlib.decompress(compressedIn, defaultBlockSize);
+
+ }
+
+ /**
+ * Reads one byte from the internal uncompressed output list of Bytes.
+ * @return The byte value from the internal list at the current read position.
+ * @throws IOException if there is an error while reading.
+ */
+ @Override
+ public int read() throws IOException {
+ if (currentByte < decompressedOutputList.size()) {
+ byte readByte = decompressedOutputList.get(currentByte).byteValue();
+ currentByte++;
+ return Byte.toUnsignedInt(readByte);
+ }
+ return -1;
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsSuper.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsSuper.java
new file mode 100644
index 0000000000..3570d95a4a
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/CramFsSuper.java
@@ -0,0 +1,164 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.data.*;
+import ghidra.util.exception.DuplicateNameException;
+
+public class CramFsSuper implements StructConverter {
+
+ private int magic;
+ private int size;
+ private int flags;
+ private int future;
+ private boolean isLE;
+ private String signature;
+ private CramFsInfo fsid;
+ private String name;
+ private CramFsInode root;
+ private List childList = new ArrayList<>();
+
+ /**
+ * Constuctor for the cramfs super block.
+ * @param reader binary reader for the super block.
+ * @throws IOException if there is an error when reading the super block.
+ */
+ public CramFsSuper(BinaryReader reader) throws IOException {
+ magic = reader.readNextInt();
+ size = reader.readNextInt();
+ flags = reader.readNextInt();
+ future = reader.readNextInt();
+ signature = reader.readNextAsciiString(CramFsConstants.HEADER_STRING_LENGTH);
+ fsid = new CramFsInfo(reader);
+ name = reader.readNextAsciiString(CramFsConstants.HEADER_STRING_LENGTH);
+ root = new CramFsInode(reader);
+ isLE = reader.isLittleEndian();
+ for (int i = 0; i < fsid.getFiles() - 1; i++) {
+ childList.add(new CramFsInode(reader));
+ }
+ }
+
+ /**
+ * Checks to see if the CRAMFS_FLAG_EXT_BLOCK_POINTERS is set or not
+ * @return boolean value for if the flag is set or not
+ */
+ public boolean isExtensionsBlockPointerFlagEnabled() {
+ return (flags &
+ CramFsConstants.CRAMFS_FLAG_EXT_BLOCK_POINTERS) == CramFsConstants.CRAMFS_FLAG_EXT_BLOCK_POINTERS;
+ }
+
+ /**
+ * Returns the magic number.
+ * @return the magic number
+ */
+ public int getMagic() {
+ return magic;
+ }
+
+ /**
+ * Returns the size of the super block.
+ * @return the size of the super block
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the super block flags.
+ * @return the super block flags.
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Returns the future.
+ * @return the future.
+ */
+ public int getFuture() {
+ return future;
+ }
+
+ /**
+ * Returns if the super block is little endian or not.
+ * @return true if the super block is little endian, or false if not.
+ */
+ public boolean isLittleEndian() {
+ return isLE;
+ }
+
+ /**
+ * Returns the super block signature.
+ * @return the super block signature.
+ */
+ public String getSignature() {
+ return signature;
+ }
+
+ /**
+ * Returns the file system identifier.
+ * @return the file system identifier.
+ */
+ public CramFsInfo getFsid() {
+ return fsid;
+ }
+
+ /**
+ * Returns the name of the super block.
+ * @return the name of the super block.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the root node of the super block.
+ * @return the root node of the super block.
+ */
+ public CramFsInode getRoot() {
+ return root;
+ }
+
+ /**
+ * Returns the childList of the super block.
+ * @return the childList of the super block.
+ */
+ public List getChildList() {
+ return childList;
+ }
+
+ @Override
+ public DataType toDataType() throws DuplicateNameException, IOException {
+ Structure struct = new StructureDataType("cramfs_super", 0);
+ struct.add(DWORD, "magic", null);
+ struct.add(DWORD, "size", null);
+ struct.add(DWORD, "flags", null);
+ struct.add(DWORD, "future", null);
+ struct.add(STRING, CramFsConstants.HEADER_STRING_LENGTH, "signature", null);
+ struct.add(fsid.toDataType(), "fsid", null);
+ struct.add(STRING, CramFsConstants.HEADER_STRING_LENGTH, "name", null);
+ struct.add(root.toDataType(), "root", null);
+
+ return struct;
+ }
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/LazyCramFsInputStream.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/LazyCramFsInputStream.java
new file mode 100644
index 0000000000..6f6aa6c23f
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cramfs/LazyCramFsInputStream.java
@@ -0,0 +1,54 @@
+/* ###
+ * 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.cramfs;
+
+import java.io.*;
+
+import ghidra.app.util.bin.ByteProvider;
+
+public class LazyCramFsInputStream extends InputStream {
+ private CramFsBlockReader cramFsBlockReader;
+ private InputStream currentDecompressedBlockInputStream = new ByteArrayInputStream(new byte[0]);
+ private int currentCompressedBlockIndex;
+
+ /**
+ * Constructor for lazy cramfs input stream.
+ * @param provider byte provider for the input stream.
+ * @param cramfsInode the parent node for the input stream.
+ * @param isLittleEndian returns true if the input stream is little endian.
+ * @throws IOException if there is an error when creating the input stream.
+ */
+ public LazyCramFsInputStream(ByteProvider provider, CramFsInode cramfsInode,
+ boolean isLittleEndian) throws IOException {
+ cramFsBlockReader = new CramFsBlockReader(provider, cramfsInode, isLittleEndian);
+ }
+
+ @Override
+ public int read() throws IOException {
+
+ int byteRead = currentDecompressedBlockInputStream.read();
+
+ if (byteRead == -1) {
+ if (currentCompressedBlockIndex < cramFsBlockReader.getNumBlockPointers()) {
+ currentDecompressedBlockInputStream =
+ cramFsBlockReader.readDataBlockDecompressed(currentCompressedBlockIndex);
+ byteRead = currentDecompressedBlockInputStream.read();
+ currentCompressedBlockIndex++;
+ }
+ }
+ return byteRead;
+ }
+}