diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ExtentsByteProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ExtentsByteProvider.java
new file mode 100644
index 0000000000..4e72eda7c4
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ExtentsByteProvider.java
@@ -0,0 +1,175 @@
+/* ###
+ * 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.app.util.bin;
+
+import java.io.*;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import ghidra.formats.gfilesystem.FSRL;
+
+/**
+ * A {@link ByteProvider} that is a concatenation of sub-ranges of another ByteProvider, also
+ * allowing for non-initialized (sparse) regions.
+ *
+ * Not thread-safe when extents are being added.
+ */
+public class ExtentsByteProvider implements ByteProvider {
+
+ private ByteProvider delegate;
+ /**
+ * TreeMap of this-provider offsets to the delegate-provider's offsets.
+ *
+ * Each extent/region in the delegate provider is defined by the gap between
+ * adjacent offsetMap entries. The last entry is bounded by the total
+ * length of the provider as specified by the length field.
+ */
+ private TreeMap offsetMap = new TreeMap<>(); // this-provider offset -> delegate-provider offset
+ private long length;
+ private FSRL fsrl;
+
+ /**
+ * Creates a new {@link ExtentsByteProvider}.
+ *
+ * @param provider {@link ByteProvider} to wrap
+ * @param fsrl {@link FSRL} of the byte provider
+ */
+ public ExtentsByteProvider(ByteProvider provider, FSRL fsrl) {
+ this.delegate = provider;
+ this.fsrl = fsrl;
+ }
+
+ /**
+ * Adds an extent to the current end of this instance.
+ *
+ * @param offset long byte offset in the delegate ByteProvider
+ * @param extentLen long length of the extent region in the delegate ByteProvider
+ */
+ public void addExtent(long offset, long extentLen) {
+ if (extentLen <= 0) {
+ throw new IllegalArgumentException();
+ }
+ offsetMap.put(length, offset);
+ length += extentLen;
+ }
+
+ /**
+ * Adds a sparse extent to the current end of this instance.
+ *
+ * @param extentLen long length of the sparse extent region
+ */
+ public void addSparseExtent(long extentLen) {
+ addExtent(-1, extentLen);
+ }
+
+ @Override
+ public File getFile() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public FSRL getFSRL() {
+ return fsrl;
+ }
+
+ @Override
+ public String getAbsolutePath() {
+ return null;
+ }
+
+ @Override
+ public long length() throws IOException {
+ return length;
+ }
+
+ @Override
+ public boolean isValidIndex(long index) {
+ return 0 <= index && index < length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override
+ public byte readByte(long index) throws IOException {
+ ensureBounds(index, 1);
+
+ Entry entry = offsetMap.floorEntry(index);
+ long extentStart = entry.getKey();
+ long extentOffset = index - extentStart;
+ long delegateExtentStart = entry.getValue();
+
+ return (delegateExtentStart != -1)
+ ? delegate.readByte(delegateExtentStart + extentOffset)
+ : 0;
+ }
+
+ @Override
+ public byte[] readBytes(long index, long longCount) throws IOException {
+ if (longCount >= Integer.MAX_VALUE) {
+ throw new IOException("Unable to read " + longCount + " bytes at once");
+ }
+ ensureBounds(index, longCount);
+
+ int count = (int) longCount;
+ byte[] result = new byte[count];
+ int bytesRead = 0;
+ while (bytesRead < count) {
+ long offsetToRead = index + bytesRead;
+ Entry entry = offsetMap.floorEntry(offsetToRead);
+ Entry nextEntry = offsetMap.higherEntry(entry.getKey());
+
+ long extentStart = entry.getKey();
+ long extentOffset = offsetToRead - extentStart;
+ long extentEnd = (nextEntry != null) ? nextEntry.getKey() : length;
+ long delegateExtentStart = entry.getValue();
+ int bytesToRead =
+ (int) Math.min(count - bytesRead, extentEnd - extentStart - extentOffset);
+ if (delegateExtentStart != -1) {
+ long delegateOffsetToRead = delegateExtentStart + extentOffset;
+ byte[] extentBytes = delegate.readBytes(delegateOffsetToRead, bytesToRead);
+ System.arraycopy(extentBytes, 0, result, bytesRead, bytesToRead);
+ }
+ else {
+ // the extent was not present, result will be 0's
+ }
+ bytesRead += bytesToRead;
+ }
+ return result;
+ }
+
+ @Override
+ public InputStream getInputStream(long index) throws IOException {
+ return new ByteProviderInputStream(this, 0, length);
+ }
+
+ private void ensureBounds(long index, long count) throws IOException {
+ if (index < 0 || index > length) {
+ throw new IOException("Invalid index: " + index);
+ }
+ if (index + count > length) {
+ throw new IOException("Unable to read past EOF: " + index + ", " + count);
+ }
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/ExtentsByteProviderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/ExtentsByteProviderTest.java
new file mode 100644
index 0000000000..8bb53f67f0
--- /dev/null
+++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/ExtentsByteProviderTest.java
@@ -0,0 +1,214 @@
+/* ###
+ * 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.app.util.bin;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class ExtentsByteProviderTest {
+ private ByteArrayProvider bap(int... values) {
+ byte[] bytes = new byte[values.length];
+ for (int i = 0; i < values.length; i++) {
+ bytes[i] = (byte) values[i];
+ }
+ return new ByteArrayProvider(bytes);
+ }
+
+ /*
+ * "NN 01 NN 03 NN 05 NN 07 NN 09"... (NN = blockNumber, 00-FF = offset in block)
+ */
+ private ByteArrayProvider patternedBAP(int bs, int count) {
+ byte[] bytes = new byte[bs * count];
+ for (int blockNum = 0; blockNum < count; blockNum++) {
+ int blockStart = blockNum * bs;
+ Arrays.fill(bytes, blockStart, blockStart + bs, (byte) blockNum);
+ for (int i = 1; i < bs; i += 2) {
+ bytes[i + blockStart] = (byte) (i % 256);
+ }
+ }
+ return new ByteArrayProvider(bytes);
+ }
+
+ @Test(expected = IOException.class)
+ public void testEmptyExtentBP() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(bap(55), null)) {
+ ebp.readByte(0);
+ }
+ }
+
+ @Test
+ public void testExtentBP_SingleByteRead() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(10, 10);
+ ebp.addExtent(0, 1);
+ ebp.addExtent(0, 10);
+
+ assertEquals(21, ebp.length());
+
+ assertEquals(0x01, ebp.readByte(0));
+ assertEquals(0x01, ebp.readByte(1));
+ assertEquals(0x01, ebp.readByte(2));
+ assertEquals(0x03, ebp.readByte(3));
+ assertEquals(0x09, ebp.readByte(9));
+
+ assertEquals(0x00, ebp.readByte(11));
+ assertEquals(0x01, ebp.readByte(12));
+ assertEquals(0x00, ebp.readByte(13));
+ assertEquals(0x03, ebp.readByte(14));
+ assertEquals(0x09, ebp.readByte(20));
+
+ try {
+ ebp.readByte(21);
+ fail();
+ }
+ catch (IOException e) {
+ // good
+ }
+ }
+ }
+
+ @Test
+ public void testExtentBP_MultiByteRead() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(10, 10);
+ ebp.addExtent(0, 1);
+ ebp.addExtent(0, 10);
+
+ assertEquals(21, ebp.length());
+
+ byte[] bytes = ebp.readBytes(0, 21);
+ assertEquals(0x01, bytes[0]);
+ assertEquals(0x01, bytes[1]);
+ assertEquals(0x01, bytes[2]);
+ assertEquals(0x03, bytes[3]);
+ assertEquals(0x09, bytes[9]);
+
+ assertEquals(0x00, bytes[11]);
+ assertEquals(0x01, bytes[12]);
+ assertEquals(0x00, bytes[13]);
+ assertEquals(0x03, bytes[14]);
+ assertEquals(0x09, bytes[20]);
+ }
+ }
+
+ @Test
+ public void testExtentBP_MisalignedMultiByteRead() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(10, 10);
+ ebp.addExtent(0, 10);
+
+ assertEquals(20, ebp.length());
+
+ byte[] bytes = ebp.readBytes(5, 10);
+ assertEquals(0x05, bytes[0]);
+ assertEquals(0x01, bytes[1]);
+ assertEquals(0x07, bytes[2]);
+ assertEquals(0x01, bytes[3]);
+ assertEquals(0x09, bytes[4]);
+ assertEquals(0x00, bytes[5]);
+ assertEquals(0x01, bytes[6]);
+ assertEquals(0x00, bytes[7]);
+ assertEquals(0x03, bytes[8]);
+ assertEquals(0x00, bytes[9]);
+ }
+ }
+
+ @Test
+ public void testSmallExtentBP() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(10, 1);
+ ebp.addExtent(0, 1);
+
+ assertEquals(2, ebp.length());
+
+ assertEquals(0x01, ebp.readByte(0));
+ assertEquals(0x00, ebp.readByte(1));
+
+ try {
+ ebp.readByte(3);
+ fail();
+ }
+ catch (IOException e) {
+ // good
+ }
+ }
+ }
+
+ @Test
+ public void testExtentBP_SparseMultiByteRead() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(-1, 5);
+
+ assertEquals(5, ebp.length());
+
+ byte[] bytes = ebp.readBytes(0, 5);
+ assertEquals(0x00, bytes[0]);
+ assertEquals(0x00, bytes[1]);
+ assertEquals(0x00, bytes[2]);
+ assertEquals(0x00, bytes[3]);
+ assertEquals(0x00, bytes[4]);
+ }
+ }
+
+ @Test
+ public void testExtentBP_SparseSingleByteRead() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(-1, 5);
+
+ assertEquals(5, ebp.length());
+
+ assertEquals(0x00, ebp.readByte(0));
+ assertEquals(0x00, ebp.readByte(1));
+ assertEquals(0x00, ebp.readByte(2));
+ assertEquals(0x00, ebp.readByte(3));
+ assertEquals(0x00, ebp.readByte(4));
+ }
+ }
+
+ @Test
+ public void testExtentBP_MixedSparseMultiByteRead() throws IOException {
+ try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
+ ebp.addExtent(10, 10);
+ ebp.addExtent(-1, 5);
+ ebp.addExtent(0, 10);
+
+ assertEquals(25, ebp.length());
+
+ byte[] bytes = ebp.readBytes(0, 25);
+ assertEquals(0x01, bytes[0]);
+ assertEquals(0x01, bytes[1]);
+ assertEquals(0x01, bytes[2]);
+ assertEquals(0x03, bytes[3]);
+ assertEquals(0x09, bytes[9]);
+
+ assertEquals(0x00, bytes[10]);
+ assertEquals(0x00, bytes[11]);
+ assertEquals(0x00, bytes[12]);
+ assertEquals(0x00, bytes[13]);
+ assertEquals(0x00, bytes[14]);
+
+ assertEquals(0x00, bytes[15]);
+ assertEquals(0x01, bytes[16]);
+ assertEquals(0x00, bytes[17]);
+ assertEquals(0x03, bytes[18]);
+ assertEquals(0x09, bytes[24]);
+ }
+ }
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java
index f55510ba29..42283008f4 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java
@@ -198,10 +198,7 @@ public class Ext4Analyzer extends FileFormatAnalyzer {
List entries = i_block.getExtentEntries();
for( int i = 0; i < numEntries; i++ ) {
Ext4Extent extent = entries.get(i);
- int low = extent.getEe_start_lo();
- int high = extent.getEe_start_hi();
- int blockNumber = (high << 16) | low;
- long offset = blockNumber * blockSize;
+ long offset = extent.getExtentStartBlockNumber() * blockSize;
reader.setPointerIndex(offset);
Address address = toAddr(program, offset);
if( isDirEntry2 ) {
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java
index e1e2d0dceb..93b247122a 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java
@@ -15,16 +15,12 @@
*/
package ghidra.file.formats.ext4;
-import ghidra.app.util.bin.BinaryReader;
-import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.bin.StructConverter;
-import ghidra.program.model.data.DataType;
-import ghidra.program.model.data.Structure;
-import ghidra.program.model.data.StructureDataType;
-import ghidra.util.exception.DuplicateNameException;
-
import java.io.IOException;
+import ghidra.app.util.bin.*;
+import ghidra.program.model.data.*;
+import ghidra.util.exception.DuplicateNameException;
+
public class Ext4Extent implements StructConverter {
private int ee_block;
@@ -59,6 +55,33 @@ public class Ext4Extent implements StructConverter {
return ee_start_lo;
}
+ /**
+ * Returns the number of blocks this extent contains.
+ *
+ * @return number of blocks in this extent
+ */
+ public int getExtentBlockCount() {
+ return Short.toUnsignedInt(ee_len);
+ }
+
+ /**
+ * Returns the stream block number of where this extent starts.
+ *
+ * @return block number (in the constructed stream) of this extent
+ */
+ public long getStreamBlockNumber() {
+ return Integer.toUnsignedLong(ee_block);
+ }
+
+ /**
+ * Returns the block number of where the data for this extent is stored.
+ *
+ * @return starting block number of where data for this extent is stored
+ */
+ public long getExtentStartBlockNumber() {
+ return Short.toUnsignedLong(ee_start_hi) << 32 | Integer.toUnsignedLong(ee_start_lo);
+ }
+
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType("ext4_extent", 0);
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java
index 92c131e247..2bc33985d7 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java
@@ -15,13 +15,13 @@
*/
package ghidra.file.formats.ext4;
-import java.io.*;
+import java.io.IOException;
+import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
-import ghidra.app.util.bin.BinaryReader;
-import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.*;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.util.NumericUtilities;
@@ -113,8 +113,8 @@ public class Ext4FileSystem implements GFileSystem {
if (parent == null) {
parent = fsih.getRootDir();
}
- parent = fsih.storeFileWithParent(name, parent, -1, true,
- (inode.getI_size_high() << 32) | inode.getI_size_lo(), new Ext4File(name, inode));
+ parent = fsih.storeFileWithParent(name, parent, -1, true, inode.getSize(),
+ new Ext4File(name, inode));
}
if ((inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) == 0) {
return;
@@ -142,10 +142,7 @@ public class Ext4FileSystem implements GFileSystem {
for (int i = 0; i < numEntries; i++) {
monitor.checkCanceled();
Ext4Extent extent = entries.get(i);
- long low = extent.getEe_start_lo() & 0xffffffffL;
- long high = extent.getEe_start_hi() & 0xffffffffL;
- long blockNumber = (high << 16) | low;
- long offset = blockNumber * blockSize;
+ long offset = extent.getExtentStartBlockNumber() * blockSize;
reader.setPointerIndex(offset);
if (isDirEntry2) {
processDirEntry2(reader, superBlock, inodes, parent, monitor, extent, offset);
@@ -241,10 +238,9 @@ public class Ext4FileSystem implements GFileSystem {
private void storeFile(Ext4Inode[] inodes, Ext4DirEntry dirEnt, GFile parent) {
int fileInodeNum = dirEnt.getInode();
Ext4Inode fileInode = inodes[fileInodeNum];
- long fileSize = (fileInode.getI_size_high() << 32) | fileInode.getI_size_lo();
fsih.storeFileWithParent(dirEnt.getName(), parent, -1,
- (fileInode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR, fileSize,
- new Ext4File(dirEnt.getName(), fileInode));
+ (fileInode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR,
+ fileInode.getSize(), new Ext4File(dirEnt.getName(), fileInode));
inodes[fileInodeNum] = null;
}
@@ -254,9 +250,8 @@ public class Ext4FileSystem implements GFileSystem {
if (fileInode == null) {
return;//TODO
}
- long fileSize = (fileInode.getI_size_high() << 32) | fileInode.getI_size_lo();
fsih.storeFileWithParent(dirEnt2.getName(), parent, -1,
- dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_DIRECTORY, fileSize,
+ dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_DIRECTORY, fileInode.getSize(),
new Ext4File(dirEnt2.getName(), fileInode));
inodes[fileInodeNum] = null;
}
@@ -284,7 +279,7 @@ public class Ext4FileSystem implements GFileSystem {
}
Ext4Inode inode = ext4File.getInode();
String info = "";
- long size = (inode.getI_size_high() << 32) | inode.getI_size_lo();
+ long size = inode.getSize();
if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) {
Ext4IBlock block = inode.getI_block();
byte[] extra = block.getExtra();
@@ -299,27 +294,8 @@ public class Ext4FileSystem implements GFileSystem {
@Override
public InputStream getInputStream(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
- Ext4File extFile = fsih.getMetadata(file);
- if (extFile == null) {
- return null;
- }
- Ext4Inode inode = extFile.getInode();
- if (inode == null) {
- return null;
- }
-
- if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) {
- throw new IOException(extFile.getName() + " is a directory.");
- }
-
- if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) {
- inode = resolveSymLink(file);
- if (inode == null) {
- throw new IOException(extFile.getName() + " is a broken symlink.");
- }
- }
-
- return getInputStream(inode);
+ ByteProvider bp = getByteProvider(file, monitor);
+ return (bp != null) ? new ByteProviderInputStream(bp, 0, bp.length()) : null;
}
private static final int MAX_SYMLINK_LOOKUP_COUNT = 100;
@@ -351,57 +327,76 @@ public class Ext4FileSystem implements GFileSystem {
return null;
}
- private InputStream getInputStream(Ext4Inode inode) throws IOException {
- int i_size_lo = inode.getI_size_lo();
- int i_size_high = inode.getI_size_high();
- long size = (i_size_high << 32) | i_size_lo;
+ private Ext4Inode getInodeFor(GFile file) throws IOException {
+ Ext4File extFile = fsih.getMetadata(file);
+ if (extFile == null) {
+ return null;
+ }
+ Ext4Inode inode = extFile.getInode();
+ if (inode == null) {
+ return null;
+ }
- boolean usesExtents = (inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0;
- if (usesExtents) {
- Ext4IBlock i_block = inode.getI_block();
- Ext4ExtentHeader header = i_block.getHeader();
- if (header.getEh_depth() == 0) {
- List entries = i_block.getExtentEntries();
- return concatenateExtents(entries, size);
+ if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) {
+ inode = resolveSymLink(file);
+ if (inode == null) {
+ throw new IOException(extFile.getName() + " is a broken symlink.");
}
}
- return null;
+ return inode;
}
/**
- * The file is spread throughout the ext4 file, so
- * concatenate each extent into one contiguous stream.
+ * Returns a {@link ByteProvider} that supplies the bytes of the requested file.
*
- * TODO better memory management? currently loads entire file into memory.
+ * @param file {@link GFile} to get
+ * @param monitor {@link TaskMonitor} to cancel
+ * @return {@link ByteProvider} containing the bytes of the requested file, caller is
+ * responsible for closing the ByteProvider
+ * @throws IOException if error
*/
- private InputStream concatenateExtents(List entries, long actualSize)
- throws IOException {
- if (actualSize > Integer.MAX_VALUE) {
- throw new IOException(
- "File is >2GB, too large to extract. Please report to Ghidra team.");
+ public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException {
+ Ext4Inode inode = getInodeFor(file);
+ if (inode == null) {
+ return null;
}
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
- for (int i = 0; i < entries.size(); ++i) {
- Ext4Extent extent = entries.get(i);
-
- long low = extent.getEe_start_lo() & 0xffffffffL;
- long high = extent.getEe_start_hi() & 0xffffffffL;
- long blockNumber = (high << 16) | low;
- long extentOffset = blockNumber * blockSize;
- long extentSize = (extent.getEe_len() & 0xffffL) * blockSize;
-
- try {
- byte[] extentBytes = provider.readBytes(extentOffset, extentSize);
- baos.write(extentBytes);
- }
- catch (IOException e) {
- // ignore
- }
+ if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) {
+ throw new IOException(file.getName() + " is a directory.");
}
- return new ByteArrayInputStream(baos.toByteArray(), 0, (int) actualSize);
+ boolean usesExtents = (inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0;
+ if (!usesExtents) {
+ throw new IOException("Unsupported file storage: not EXT4_EXTENTS: " + file.getPath());
+ }
+
+ Ext4IBlock i_block = inode.getI_block();
+ Ext4ExtentHeader header = i_block.getHeader();
+ if (header.getEh_depth() != 0) {
+ throw new IOException("Unsupported file storage: eh_depth: " + file.getPath());
+ }
+
+ long fileSize = inode.getSize();
+ ExtentsByteProvider ebp = new ExtentsByteProvider(provider, file.getFSRL());
+ for (Ext4Extent extent : i_block.getExtentEntries()) {
+ long startPos = extent.getStreamBlockNumber() * blockSize;
+ long providerOfs = extent.getExtentStartBlockNumber() * blockSize;
+ long extentLen = extent.getExtentBlockCount() * blockSize;
+ if (ebp.length() < startPos) {
+ ebp.addSparseExtent(startPos - ebp.length());
+ }
+ if (ebp.length() + extentLen > fileSize) {
+ // the last extent may have a trailing partial block
+ extentLen = fileSize - ebp.length();
+ }
+
+ ebp.addExtent(providerOfs, extentLen);
+ }
+ if (ebp.length() < fileSize) {
+ // trailing sparse. not sure if possible.
+ ebp.addSparseExtent(fileSize - ebp.length());
+ }
+ return ebp;
}
private Ext4Inode[] getInodes(BinaryReader reader, Ext4SuperBlock superBlock,
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java
index bd3e0b21bf..494b5f9449 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java
@@ -15,17 +15,12 @@
*/
package ghidra.file.formats.ext4;
-import ghidra.app.util.bin.BinaryReader;
-import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.bin.StructConverter;
-import ghidra.program.model.data.ArrayDataType;
-import ghidra.program.model.data.DataType;
-import ghidra.program.model.data.Structure;
-import ghidra.program.model.data.StructureDataType;
-import ghidra.util.exception.DuplicateNameException;
-
import java.io.IOException;
+import ghidra.app.util.bin.*;
+import ghidra.program.model.data.*;
+import ghidra.util.exception.DuplicateNameException;
+
public class Ext4Inode implements StructConverter {
private short i_mode;
@@ -199,6 +194,15 @@ public class Ext4Inode implements StructConverter {
return i_projid;
}
+ /**
+ * Returns the size of this file.
+ *
+ * @return size of this file
+ */
+ public long getSize() {
+ return Integer.toUnsignedLong(i_size_high) << 32 | Integer.toUnsignedLong(i_size_lo);
+ }
+
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
DataType iBlockDataType = i_block.toDataType();
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java
index f44b1eb215..663aecff16 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java
@@ -21,17 +21,13 @@ import java.util.List;
import ghidra.app.cmd.comments.SetCommentCmd;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.services.ProgramManager;
-import ghidra.app.util.bin.BinaryReader;
-import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.bin.MemoryByteProvider;
+import ghidra.app.util.bin.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DataType;
-import ghidra.program.model.listing.CodeUnit;
-import ghidra.program.model.listing.Data;
-import ghidra.program.model.listing.Program;
+import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
@@ -260,10 +256,7 @@ public class NewExt4Analyzer extends FileFormatAnalyzer {
if ( iBlock != null ) {
for ( Ext4Extent extent : iBlock.getExtentEntries( ) ) {
monitor.checkCanceled( );
- long lo = extent.getEe_start_lo( ) & 0xffffffffL;
- long hi = extent.getEe_start_hi( ) & 0xffffffffL;
- long value = ( hi << 32 ) | lo;
- long destination = value * blockSize;
+ long destination = extent.getExtentStartBlockNumber() * blockSize;
comment += "Extent: 0x" + Long.toHexString( destination ) + "\n";
}
}
@@ -346,10 +339,7 @@ public class NewExt4Analyzer extends FileFormatAnalyzer {
List entries = i_block.getExtentEntries();
for ( int i = 0; i < numEntries; i++ ) {
Ext4Extent extent = entries.get( i );
- long lo = extent.getEe_start_lo( ) & 0xffffffffL;
- long hi = extent.getEe_start_hi( ) & 0xffffffffL;
- long value = ( hi << 32 ) | lo;
- long offset = value * blockSize;
+ long offset = extent.getExtentStartBlockNumber() * blockSize;
reader.setPointerIndex(offset);
Address address = toAddr( program, offset );
if ( isDirEntry2 ) {