/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.fs.ext4; import java.io.IOException; import org.jnode.fs.ext2.Ext2FileSystem; import org.jnode.util.LittleEndian; /** * An ext4 extent header. * * @author Luke Quinane */ public class ExtentHeader { /** * The length of an extent header. */ public static final int EXTENT_HEADER_LENGTH = 12; /** * The magic number for an extent header. */ public static final int MAGIC = 0xf30a; /** * The data for the header. */ private final byte[] data; /** * The cache copy of the index entries. */ private ExtentIndex[] indexEntries; /** * The cache copy of the extent entries. */ private Extent[] extentEntries; /** * Create an extent header object. */ public ExtentHeader(byte[] data) throws IOException { this.data = new byte[data.length]; System.arraycopy(data, 0, this.data, 0, data.length); if (getMagic() != ExtentHeader.MAGIC) { throw new IOException("Extent had the wrong magic: " + getMagic()); } } public int getMagic() { return LittleEndian.getUInt16(data, 0); } public int getEntryCount() { return LittleEndian.getUInt16(data, 2); } public int getMaximumEntryCount() { return LittleEndian.getUInt16(data, 4); } public int getDepth() { return LittleEndian.getUInt16(data, 6); } public ExtentIndex[] getIndexEntries() { if (getDepth() == 0) { throw new IllegalStateException("Trying to read index entries from a leaf."); } if (indexEntries == null) { indexEntries = new ExtentIndex[getEntryCount()]; int offset = EXTENT_HEADER_LENGTH; for (int i = 0; i < getEntryCount(); i++) { byte[] indexBuffer = new byte[ExtentIndex.EXTENT_INDEX_LENGTH]; System.arraycopy(data, offset, indexBuffer, 0, indexBuffer.length); indexEntries[i] = new ExtentIndex(indexBuffer); offset += ExtentIndex.EXTENT_INDEX_LENGTH; } } return indexEntries; } public Extent[] getExtentEntries() { if (getDepth() != 0) { throw new IllegalStateException("Trying to read extent entries from a non-leaf."); } if (extentEntries == null) { extentEntries = new Extent[getEntryCount()]; int offset = EXTENT_HEADER_LENGTH; for (int i = 0; i < getEntryCount(); i++) { byte[] indexBuffer = new byte[Extent.EXTENT_LENGTH]; System.arraycopy(data, offset, indexBuffer, 0, indexBuffer.length); extentEntries[i] = new Extent(indexBuffer); offset += Extent.EXTENT_LENGTH; } } return extentEntries; } public long getBlockNumber(Ext2FileSystem fs, long index) throws IOException { if (getDepth() > 0) { ExtentIndex extentIndex = binarySearchIndexes(index, getIndexEntries()); byte[] indexData = fs.getBlock(extentIndex.getLeafLow()); ExtentHeader indexHeader = new ExtentHeader(indexData); return indexHeader.getBlockNumber(fs, index); } else { Extent extent = binarySearchExtents(index, getExtentEntries()); return index - extent.getBlockIndex() + extent.getStartLow(); } } /** * Performs a binary search in the extent indexes. * * @param index the index of the block to match. * @param indexes the indexes to search in. * @return the matching index. */ private ExtentIndex binarySearchIndexes(long index, ExtentIndex[] indexes) { int lowIndex = 0; int highIndex = indexes.length - 1; ExtentIndex extentIndex = null; while (lowIndex <= highIndex) { int middle = lowIndex + (highIndex - lowIndex) / 2; extentIndex = indexes[middle]; if (index < extentIndex.getBlockIndex()) { highIndex = middle - 1; } else { lowIndex = middle + 1; } } return indexes[Math.max(0, lowIndex - 1)]; } /** * Performs a binary search in the extents. * * @param index the index of the block to match. * @param extents the extents to search in. * @return the matching extent. */ private Extent binarySearchExtents(long index, Extent[] extents) { int lowIndex = 0; int highIndex = extents.length - 1; Extent extent = null; while (lowIndex <= highIndex) { int middle = lowIndex + (highIndex - lowIndex) / 2; extent = extents[middle]; if (index < extent.getBlockIndex()) { highIndex = middle - 1; } else { lowIndex = middle + 1; } } return extents[Math.max(0, lowIndex - 1)]; } @Override public String toString() { return String .format("ExtentHeader: depth:%d entries:%d/%d", getDepth(), getEntryCount(), getMaximumEntryCount()); } }