From: <ls...@us...> - 2008-12-15 08:40:47
|
Revision: 4789 http://jnode.svn.sourceforge.net/jnode/?rev=4789&view=rev Author: lsantha Date: 2008-12-15 08:40:42 +0000 (Mon, 15 Dec 2008) Log Message: ----------- Support for compressed files in NTFS, by Daniel Noll. Modified Paths: -------------- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java trunk/fs/src/fs/org/jnode/fs/ntfs/DataRun.java trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSAttribute.java trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java Added Paths: ----------- trunk/fs/src/fs/org/jnode/fs/ntfs/CompressedDataRun.java trunk/fs/src/fs/org/jnode/fs/ntfs/DataRunInterface.java Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java 2008-12-14 21:49:21 UTC (rev 4788) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -27,7 +27,7 @@ /** * Data structure containing a list of {@link AttributeListEntry} entries. * - * @author Daniel Noll (da...@nu...) + * @author Daniel Noll (da...@no...) */ final class AttributeListBlock extends NTFSStructure { @@ -39,6 +39,7 @@ /** * @param data binary data for the block. * @param offset the offset into the binary data. + * @param length the length of the attribute list block, or 0 if unknown. */ public AttributeListBlock(byte[] data, int offset, long length) { super(data, offset); Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java 2008-12-14 21:49:21 UTC (rev 4788) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -23,7 +23,7 @@ /** - * @author Daniel Noll (da...@nu...) + * @author Daniel Noll (da...@no...) */ final class AttributeListEntry extends NTFSStructure { @@ -97,7 +97,7 @@ * @return the file sequence number. */ public long getFileSequenceNumber() { - return getUInt48(0x16); + return getUInt16(0x16); } /** Added: trunk/fs/src/fs/org/jnode/fs/ntfs/CompressedDataRun.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/CompressedDataRun.java (rev 0) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/CompressedDataRun.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -0,0 +1,336 @@ +/* + * $Id$ + * + * JNode.org + * Copyright (C) 2003-2006 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.ntfs; + +import java.io.IOException; +import java.util.Arrays; + +import org.apache.log4j.Logger; +import org.jnode.util.LittleEndian; + +/** + * @author Daniel Noll (da...@no...) + */ +final class CompressedDataRun implements DataRunInterface { + /** + * Size of a compressed block in NTFS. This is always the same even if the cluster size + * is not 4k. + */ + private static final int BLOCK_SIZE = 0x1000; + + /** + * Logger. + */ + private static final Logger log = Logger.getLogger(CompressedDataRun.class); + + /** + * The underlying data run containing the compressed data. + */ + private final DataRun compressedRun; + + /** + * The number of clusters which make up a compression unit. + */ + private final int compressionUnitSize; + + /** + * Constructs a compressed run which when read, will decrypt data found + * in the provided data run. + * + * @param compressedRun the compressed data run. + * @param compressionUnitSize the number of clusters which make up a compression unit. + */ + public CompressedDataRun(DataRun compressedRun, int compressionUnitSize) { + this.compressedRun = compressedRun; + this.compressionUnitSize = compressionUnitSize; + } + + /** + * Gets the length of the data run in clusters. + * + * @return the length of the run in clusters. + */ + public int getLength() { + return compressionUnitSize; + } + + /** + * Reads clusters from this datarun. + * + * @param vcn the VCN to read, offset from the start of the entire file. + * @param dst destination buffer. + * @param dstOffset offset into destination buffer. + * @param nrClusters number of clusters to read. + * @param clusterSize size of each cluster. + * @param volume reference to the NTFS volume structure. + * @return the number of clusters read. + * @throws IOException if an error occurs reading. + */ + public int readClusters(long vcn, byte[] dst, int dstOffset, + int nrClusters, int clusterSize, NTFSVolume volume) throws IOException { + + // Logic to determine whether we own the VCN which has been requested. + // XXX: Lifted from DataRun. Consider moving to some good common location. + final long myFirstVcn = compressedRun.getFirstVcn(); + final int myLength = getLength(); + final long myLastVcn = myFirstVcn + myLength - 1; + final long reqLastVcn = vcn + nrClusters - 1; + log.debug("me:" + myFirstVcn + "-" + myLastVcn + ", req:" + vcn + "-" + reqLastVcn); + if ((vcn > myLastVcn) || (myFirstVcn > reqLastVcn)) { + // Not my region + return 0; + } + + // Now we know it's in our data run, here's the actual fragment to read. + final long actFirstVcn = Math.max(myFirstVcn, vcn); + final int actLength = (int) (Math.min(myLastVcn, reqLastVcn) - actFirstVcn + 1); + + // This is the actual number of stored clusters after compression. + // If the number of stored clusters is the same as the compression unit size, + // then the data can be read directly without decompressing it. + final int compClusters = compressedRun.getLength(); + if (compClusters == compressionUnitSize) { + return compressedRun.readClusters(vcn, dst, dstOffset, compClusters, + clusterSize, volume); + } + + // Now we know the data is compressed. Read in the compressed block... + final int vcnOffsetWithinUnit = (int) (actFirstVcn % compressionUnitSize); + final long compFirstVcn = actFirstVcn - vcnOffsetWithinUnit; + final byte[] tempCompressed = new byte[compClusters * clusterSize]; + final int read = compressedRun.readClusters(compFirstVcn, tempCompressed, 0, + compClusters, clusterSize, volume); + if (read != compClusters) { + throw new IOException("Needed " + compClusters + " clusters but could " + + "only read " + read); + } + + // Uncompress it, and copy into the destination. + final byte[] tempUncompressed = new byte[compressionUnitSize * clusterSize]; + // XXX: We could potentially reduce the overhead by modifying the compression + // routine such that it's capable of skipping chunks that aren't needed. + uncompressUnit(tempCompressed, tempUncompressed); + + System.arraycopy(tempUncompressed, vcnOffsetWithinUnit * clusterSize, + dst, dstOffset + (int) (actFirstVcn - vcn) * clusterSize, + actLength * clusterSize); + + return actLength; + } + + /** + * Uncompresses a single unit of multiple compressed blocks. + * + * @param compressed the compressed data (in.) + * @param uncompressed the uncompressed data (out.) + * @param requiredBytes the number of bytes needed before the read can stop. + * @throws IOException if the decompression fails. + */ + private static void uncompressUnit(final byte[] compressed, + final byte[] uncompressed) throws IOException { + + // This is just a convenient way to simulate the original code's pointer arithmetic. + // I tried using buffers but positions in those are always from the beginning and + // I had to also maintain a position from the start of the current block. + final OffsetByteArray compressedData = new OffsetByteArray(compressed); + final OffsetByteArray uncompressedData = new OffsetByteArray(uncompressed); + + for (int i = 0; i * BLOCK_SIZE < uncompressed.length; i++) { + final int consumed = uncompressBlock(compressedData, uncompressedData); + + // Apple's code had this as an error but to me it looks like this simply + // terminates the sequence of compressed blocks. + if (consumed == 0) { + // At the current point in time this is already zero but if the code + // changes in the future to reuse the temp buffer, this is a good idea. + uncompressedData.zero(0, uncompressed.length - uncompressedData.offset); + break; + } + + compressedData.offset += consumed; + uncompressedData.offset += BLOCK_SIZE; + } + } + + /** + * Uncompresses a single block. + * + * @param compressed the compressed buffer (in.) + * @param uncompressed the uncompressed buffer (out.) + * @return the number of bytes consumed from the compressed buffer. + */ + private static int uncompressBlock(final OffsetByteArray compressed, + final OffsetByteArray uncompressed) { + + int pos = 0, cpos = 0; + + final int rawLen = compressed.getShort(cpos); cpos += 2; + final int len = rawLen & 0xFFF; + log.debug("ntfs_uncompblock: block length: " + len + " + 3, 0x" + + Integer.toHexString(len) + ",0x" + Integer.toHexString(rawLen)); + + if (rawLen == 0) { + // End of sequence, rest is zero. For some reason there is nothing + // of the sort documented in the Linux kernel's description of compression. + return 0; + } + + if ((rawLen & 0x8000) == 0) { + // Uncompressed chunks store length as 0xFFF always. + if ((len + 1) != BLOCK_SIZE) { + log.debug("ntfs_uncompblock: len: " + len + " instead of 0xfff"); + } + + // Copies the entire compression block as-is, need to skip the compression flag, + // no idea why they even stored it given that it isn't used. + // Darwin's version I was referring to doesn't skip this, which seems be a bug. + cpos++; + uncompressed.copyFrom(compressed, cpos, 0, len + 1); + uncompressed.zero(len + 1, BLOCK_SIZE - 1 - len); + return len + 3; + } + + while (cpos < len + 3 && pos < BLOCK_SIZE) { + byte ctag = compressed.get(cpos++); + for (int i = 0; i < 8 && pos < BLOCK_SIZE; i++) { + if ((ctag & 1) != 0) { + int j, lmask, dshift; + for (j = pos - 1, lmask = 0xFFF, dshift = 12; + j >= 0x10; j >>= 1) { + dshift--; + lmask >>= 1; + } + final int tmp = compressed.getShort(cpos); cpos += 2; + final int boff = -1 - (tmp >> dshift); + final int blen = Math.min(3 + (tmp & lmask), BLOCK_SIZE - pos); + + // Note that boff is negative. + uncompressed.copyFrom(uncompressed, pos + boff, pos, blen); + pos += blen; + } else { + uncompressed.put(pos++, compressed.get(cpos++)); + } + ctag >>= 1; + } + } + + return len + 3; + } + + /** + * Convenience class wrapping an array with its offset. An alternative to pointer + * arithmetic without going to the level of using an NIO buffer. + */ + private static class OffsetByteArray { + + /** + * The contained array. + */ + private final byte[] array; + + /** + * The current offset. + */ + private int offset; + + /** + * Constructs the offset byte array. The offset begins at zero. + * + * @param array the contained array. + */ + private OffsetByteArray(final byte[] array) { + this.array = array; + } + + /** + * Gets a single byte from the array. + * + * @param offset the offset from the contained offset. + * @return the byte. + */ + private byte get(int offset) { + return array[this.offset + offset]; + } + + /** + * Puts a single byte into the array. + * + * @param offset the offset from the contained offset. + * @param value the byte. + */ + private void put(int offset, byte value) { + array[this.offset + offset] = value; + } + + /** + * Gets a 16-bit little-endian value from the array. + * + * @param offset the offset from the contained offset. + * @return the short. + */ + private int getShort(int offset) { + return LittleEndian.getUInt16(array, this.offset + offset); + } + + /** + * Copies a slice from the provided array into our own array. Uses {@code System.arraycopy} + * where possible; if the slices overlap, copies one byte at a time to avoid a problem with + * using {@code System.arraycopy} in this situation. + * + * @param src the source offset byte array. + * @param srcOffset offset from the source array's offset. + * @param destOffset offset from our own offset. + * @param length the number of bytes to copy. + */ + private void copyFrom(OffsetByteArray src, int srcOffset, int destOffset, int length) { + int realSrcOffset = src.offset + srcOffset; + int realDestOffset = offset + destOffset; + byte[] srcArray = src.array; + byte[] destArray = array; + + // If the arrays are the same and the slices overlap we can't use the optimisation + // because System.arraycopy effectively copies to a temp area. :-( + if (srcArray == destArray && + (realSrcOffset < realDestOffset && realSrcOffset + length > realDestOffset || + realDestOffset < realSrcOffset && realDestOffset + length > realSrcOffset)) { + + for (int i = 0; i < length; i++) { + destArray[realDestOffset + i] = srcArray[realSrcOffset + i]; + } + + return; + } + + System.arraycopy(srcArray, realSrcOffset, destArray, realDestOffset, length); + } + + /** + * Zeroes out elements of the array. + * + * @param offset the offset from the contained offset. + * @param length the number of sequential bytes to zero out. + */ + private void zero(int offset, int length) { + Arrays.fill(array, this.offset + offset, this.offset + offset + length, (byte) 0); + } + } +} Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/DataRun.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/DataRun.java 2008-12-14 21:49:21 UTC (rev 4788) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/DataRun.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -27,7 +27,7 @@ /** * @author Ewout Prangsma (ep...@us...) */ -final class DataRun extends NTFSStructure { +final class DataRun extends NTFSStructure implements DataRunInterface { /** Type of this datarun */ private final int type; @@ -41,6 +41,9 @@ /** Length of datarun in clusters */ private final int length; + /** Flag indicating that the data is not stored on disk but is all zero. */ + private boolean sparse = false; + /** Size in bytes of this datarun descriptor */ private final int size; @@ -87,6 +90,7 @@ final int cluster; switch (clusterlen) { case 0x00: + sparse = true; cluster = 0; break; case 0x01: @@ -108,6 +112,17 @@ } /** + * Tests if this data run is a sparse run. Sparse runs don't actually refer to + * stored data, and are effectively a way to store a run of zeroes without storage + * penalty. + * + * @return {@code true} if the run is sparse, {@code false} if it is not. + */ + public boolean isSparse() { + return sparse; + } + + /** * @return Returns the cluster. */ public long getCluster() { @@ -137,20 +152,11 @@ * * @return Returns the vcn. */ - public final long getFirstVcn() { + public long getFirstVcn() { return this.vcn; } /** - * Sets the first VCN of this datarun. - * - * @param vcn the new VCN. - */ - final void setFirstVcn(long vcn) { - this.vcn = vcn; - } - - /** * Read clusters from this datarun. * * @param vcn @@ -171,7 +177,9 @@ final long reqLastVcn = vcn + nrClusters - 1; - log.debug("me:" + myFirstVcn + "-" + myLastVcn + ", req:" + vcn + "-" + reqLastVcn); + if (log.isDebugEnabled()) { + log.debug("me:" + myFirstVcn + "-" + myLastVcn + ", req:" + vcn + "-" + reqLastVcn); + } if ((vcn > myLastVcn) || (myFirstVcn > reqLastVcn)) { // Not my region @@ -194,10 +202,12 @@ actCluster = getCluster() + vcnDelta; } - log.debug("cluster=" + cluster + ", length=" + length + ", dstOffset=" + dstOffset); - log.debug("cnt=" + count + ", actclu=" + actCluster + ", actdstoff=" + actDstOffset); + if (log.isDebugEnabled()) { + log.debug("cluster=" + cluster + ", length=" + length + ", dstOffset=" + dstOffset); + log.debug("cnt=" + count + ", actclu=" + actCluster + ", actdstoff=" + actDstOffset); + } - if (actCluster == 0) { + if (isSparse()) { // Not really stored on disk -- sparse files, etc. Arrays.fill(dst, actDstOffset, actDstOffset + count * clusterSize, (byte) 0); } else { Added: trunk/fs/src/fs/org/jnode/fs/ntfs/DataRunInterface.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/DataRunInterface.java (rev 0) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/DataRunInterface.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -0,0 +1,52 @@ +/* + * $Id$ + * + * JNode.org + * Copyright (C) 2003-2006 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.ntfs; + +import java.io.IOException; + +/** + * @author Daniel Noll (da...@no...) + */ +interface DataRunInterface { + + /** + * Gets the length of the data run in clusters. + * + * @return the length of the run in clusters. + */ + int getLength(); + + /** + * Reads clusters from this datarun. + * + * @param vcn the VCN to read, offset from the start of the entire file. + * @param dst destination buffer. + * @param dstOffset offset into destination buffer. + * @param nrClusters number of clusters to read. + * @param clusterSize size of each cluster. + * @param volume reference to the NTFS volume structure. + * @return the number of clusters read. + * @throws IOException if an error occurs reading. + */ + public int readClusters(long vcn, byte[] dst, int dstOffset, + int nrClusters, int clusterSize, NTFSVolume volume) throws IOException; +} Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java 2008-12-14 21:49:21 UTC (rev 4788) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -33,6 +33,7 @@ * * @author Chira * @author Ewout Prangsma (ep...@us...) + * @author Daniel Noll (da...@no...) (new attribute iteration support) */ class FileRecord extends NTFSRecord { @@ -67,8 +68,22 @@ attributeListAttribute = (AttributeListAttribute) findStoredAttributeByType(NTFSAttribute.Types.ATTRIBUTE_LIST); - // check for the magic number to see if we have a filerecord + // check for the magic numberb to see if we have a filerecord if (getMagic() != Magic.FILE) { + log.debug("Invalid magic number found for FILE record: " + getMagic() + " -- dumping buffer"); + for (int off = 0; off < buffer.length; off += 32) { + StringBuilder builder = new StringBuilder(); + for (int i = off; i < off + 32 && i < buffer.length; i++) { + String hex = Integer.toHexString(buffer[i]); + while (hex.length() < 2) { + hex = '0' + hex; + } + + builder.append(' ').append(hex); + } + log.debug(builder.toString()); + } + throw new IOException("Invalid magic found: " + getMagic()); } @@ -107,7 +122,7 @@ public boolean isInUse() { return (getFlags() & 0x01) != 0; } - + /** * Is this a directory? * @@ -164,6 +179,16 @@ } /** + * Gets the reference number of this record within the MFT. This value + * is not actually stored in the record, but passed in from the outside. + * + * @return the reference number. + */ + public long getReferenceNumber() { + return referenceNumber; + } + + /** * @return Returns the updateSequenceOffset. */ public int getUpdateSequenceOffset() { @@ -189,7 +214,7 @@ /** * Gets the name of this file. * - * @return + * @return the filename. */ public String getFileName() { final FileNameAttribute fnAttr = getFileNameAttribute(); @@ -203,7 +228,7 @@ /** * Gets the filename attribute of this filerecord. * - * @return + * @return the filename attribute. */ public FileNameAttribute getFileNameAttribute() { if (fileNameAttribute == null) { @@ -213,7 +238,7 @@ } /** - * Gets an attribute in this filerecord with a given id. + * Gets the attributes stored in this file record. * * @return an iteratover over attributes stored in this file record. */ @@ -328,6 +353,14 @@ * @throws IOException if an error occurs reading from the filesystem. */ public void readData(long fileOffset, byte[] dest, int off, int len) throws IOException { + if (log.isDebugEnabled()) { + log.debug("readData: offset " + fileOffset + " length " + len + ", file record = " + this); + } + + if (len == 0) { + return; + } + // Explicitly look for the attribute with no name, to avoid getting alternate streams. // XXX: Add API for getting length and content from alternate streams. final AttributeIterator dataAttrs = findAttributesByTypeAndName(NTFSAttribute.Types.DATA, null); @@ -348,11 +381,17 @@ "b) is not large enough to read:" + len + "b"); } resData.getData(resData.getAttributeOffset() + (int) fileOffset, dest, off, len); + + if (log.isDebugEnabled()) { + log.debug("readData: read from resident data"); + } + return; } + // At this point we know that at least the first attribute is non-resident. + // calculate start and end cluster - final int clusterSize = getVolume().getClusterSize(); final long startCluster = fileOffset / clusterSize; final long endCluster = (fileOffset + len - 1) / clusterSize; @@ -380,8 +419,13 @@ attr = dataAttrs.next(); } while (attr != null); + if (log.isDebugEnabled()) { + log.debug("readData: read " + readClusters + " from non-resident attributes"); + } + if (readClusters != nrClusters) { - throw new IOException("Requested " + nrClusters + " clusters but only read " + readClusters); + throw new IOException("Requested " + nrClusters + " clusters but only read " + readClusters + + ", file record = " + this); } System.arraycopy(tmp, (int) fileOffset % clusterSize, dest, off, len); Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSAttribute.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSAttribute.java 2008-12-14 21:49:21 UTC (rev 4788) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSAttribute.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -189,7 +189,7 @@ } // check the resident flag - if (fileRecord.getUInt8(offset + 0x08) == 0) { + if (resident) { // resident return new NTFSResidentAttribute(fileRecord, offset); } else { Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java 2008-12-14 21:49:21 UTC (rev 4788) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java 2008-12-15 08:40:42 UTC (rev 4789) @@ -31,12 +31,13 @@ * * @author Chira * @author Ewout Prangsma (ep...@us...) + * @author Daniel Noll (da...@no...) (compression support) */ public class NTFSNonResidentAttribute extends NTFSAttribute { private int numberOfVCNs = 0; - private final List<DataRun> dataRuns = new ArrayList<DataRun>(); + private final List<DataRunInterface> dataRuns = new ArrayList<DataRunInterface>(); /** * @param fileRecord @@ -48,11 +49,6 @@ * process the dataruns...all non resident attributes have their data * outside. can find where using data runs */ - final int flags = getFlags(); - if (flags > 0) { - log.info("flags & 0x0001 = " + (flags & 0x0001)); - } - final int dataRunsOffset = getDataRunsOffset(); if (dataRunsOffset > 0) { readDataRuns(dataRunsOffset); @@ -86,6 +82,16 @@ } /** + * Gets the compression unit size. 2 to the power of this value is the number of clusters + * per compression unit. + * + * @return the compression unit size. + */ + public int getCompressionUnitSize() { + return getUInt16(0x22); + } + + /** * Gets the size allocated to the attribute. May be larger than the actual size of the * attribute data. * @@ -111,18 +117,55 @@ int offset = parentoffset; long previousLCN = 0; - final List<DataRun> dataruns = getDataRuns(); + final List<DataRunInterface> dataruns = getDataRuns(); long vcn = 0; + // If this attribute is compressed we will coalesce compressed/sparse + // data run pairs into a single data run object for convenience when reading. + boolean compressed = (getFlags() & 0x0001) != 0; + boolean expectingSparseRunNext = false; + int compUnitSize = 1 << getCompressionUnitSize(); + while (getUInt8(offset) != 0x0) { final DataRun dataRun = new DataRun(this, offset, vcn, previousLCN); - // map VCN-> datarun - dataruns.add(dataRun); - this.numberOfVCNs += dataRun.getLength(); + + if (compressed) { + if (dataRun.isSparse() && expectingSparseRunNext) { + // This is the sparse run which follows a compressed run. + // The number of runs it contains does not count towards the total + // as the compressed run reports holding all the runs for the pair. + // But we do need to move the offsets. Leaving this block open in case + // later it makes sense to put some logic in here. + } else if (dataRun.getLength() == compUnitSize) { + // Compressed/sparse pairs always add to the compression unit size. If + // the unit only compresses to 16, the system will store it uncompressed. + // So this whole unit is stored as-is, we'll leave it as a normal data run. + dataruns.add(dataRun); + this.numberOfVCNs += dataRun.getLength(); + vcn += dataRun.getLength(); + previousLCN = dataRun.getCluster(); + } else { + // TODO: Is it possible for the length to be GREATER than the unit size? + dataruns.add(new CompressedDataRun(dataRun, compUnitSize)); + if (dataRun.getLength() != compUnitSize) { + expectingSparseRunNext = true; + } + + this.numberOfVCNs += compUnitSize; + vcn += compUnitSize; + previousLCN = dataRun.getCluster(); + } + } else { + // map VCN-> datarun + dataruns.add(dataRun); + this.numberOfVCNs += dataRun.getLength(); + vcn += dataRun.getLength(); + previousLCN = dataRun.getCluster(); + } + offset += dataRun.getSize(); - previousLCN = dataRun.getCluster(); - vcn += dataRun.getLength(); } + // check the dataruns final int clusterSize = getFileRecord().getVolume().getClusterSize(); // Rounds up but won't work for 0, which shouldn't occur here. @@ -136,28 +179,13 @@ } /** - * @return Returns the dataRuns. + * @return Returns the data runs. */ - public List<DataRun> getDataRuns() { + private List<DataRunInterface> getDataRuns() { return dataRuns; } /** - * Appends extra data runs to this attribute, taken from another attribute. - * The starting VCN of the added runs are repositioned such the new runs line - * up with the end of the runs already contained in this attribute. - * - * @param dataRuns the data runs to append. - */ - public void appendDataRuns(List<DataRun> dataRuns) { - for (DataRun dataRun : dataRuns) { - dataRun.setFirstVcn(this.numberOfVCNs); - this.dataRuns.add(dataRun); - this.numberOfVCNs += dataRun.getLength(); - } - } - - /** * Read a number of clusters starting from a given virtual cluster number * (vcn). * @@ -167,16 +195,30 @@ * @throws IOException */ public int readVCN(long vcn, byte[] dst, int dstOffset, int nrClusters) throws IOException { + final int flags = getFlags(); + if ((flags & 0x4000) != 0) { + throw new IOException("Reading encrypted files is not supported"); + } + + if (log.isDebugEnabled()) { + log.debug("readVCN: wants start " + vcn + " length " + nrClusters + + ", we have start " + getStartVCN() + " length " + getNumberOfVCNs()); + } + final NTFSVolume volume = getFileRecord().getVolume(); final int clusterSize = volume.getClusterSize(); - int readClusters = 0; - for (DataRun dataRun : this.getDataRuns()) { + for (DataRunInterface dataRun : this.getDataRuns()) { readClusters += dataRun.readClusters(vcn, dst, dstOffset, nrClusters, clusterSize, volume); if (readClusters == nrClusters) { break; } } + + if (log.isDebugEnabled()) { + log.debug("readVCN: read " + readClusters); + } + return readClusters; } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |