From: <ls...@us...> - 2008-12-08 08:09:16
|
Revision: 4781 http://jnode.svn.sourceforge.net/jnode/?rev=4781&view=rev Author: lsantha Date: 2008-12-08 08:08:56 +0000 (Mon, 08 Dec 2008) Log Message: ----------- NTFS data in multiple attribute lists, by Daniel Noll Modified Paths: -------------- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttribute.java trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeNonRes.java trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeRes.java 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/FileRecord.java trunk/fs/src/fs/org/jnode/fs/ntfs/IndexAllocationAttribute.java trunk/fs/src/fs/org/jnode/fs/ntfs/IndexEntry.java trunk/fs/src/fs/org/jnode/fs/ntfs/MasterFileTable.java trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSEntry.java trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSIndex.java trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttribute.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttribute.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttribute.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -22,25 +22,22 @@ package org.jnode.fs.ntfs; import java.io.IOException; -import java.util.List; +import java.util.Iterator; /** * Common interface for both resident and non-resident attribute list * attributes. * - * @author Daniel Noll (da...@nu...) + * @author Daniel Noll (da...@no...) */ interface AttributeListAttribute { /** - * Gets an entry from the attribute list. + * Gets an iterator over all the entries in the attribute list. * - * XXX: What if there are multiple? In the case I've seen, there are multiple but only the first - * one contains any data. - * - * @param attrTypeID the type of attribute to find. - * @return the attribute entry. - * @throws IOException if there is an error reading the attribute's non-resident data. + * @return an iterator of all attribute list entries. + * @throws IOException if there is an error reading the attribute's data. */ - List<AttributeListEntry> getEntries(int attrTypeID) throws IOException; + Iterator<AttributeListEntry> getAllEntries() throws IOException; + } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeNonRes.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeNonRes.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeNonRes.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -22,14 +22,14 @@ package org.jnode.fs.ntfs; import java.io.IOException; -import java.util.List; +import java.util.Iterator; /** * $ATTRIBUTE_LIST attribute, non-resident version. * * XXX: Is there a sensible way we can merge this with the resident version? * - * @author Daniel Noll (da...@nu...) + * @author Daniel Noll (da...@no...) */ final class AttributeListAttributeNonRes extends NTFSNonResidentAttribute implements AttributeListAttribute { @@ -43,16 +43,12 @@ } /** - * Gets an entry from the attribute list. + * Gets an iterator over all the entries in the attribute list. * - * XXX: What if there are multiple? In the case I've seen, there are multiple but only the first - * one contains any data. - * - * @param attrTypeID the type of attribute to find. - * @return the attribute entry. - * @throws IOException if there is an error reading the attribute's non-resident data. + * @return an iterator of all attribute list entries. + * @throws IOException if there is an error reading the attribute's data. */ - public List<AttributeListEntry> getEntries(int attrTypeID) throws IOException { + public Iterator<AttributeListEntry> getAllEntries() throws IOException { // Read the actual data from wherever it happens to be located. // TODO: Consider handling multiple data runs separately instead // of "glueing" them all together like this. @@ -60,7 +56,6 @@ final byte[] data = new byte[nrClusters * getFileRecord().getVolume().getClusterSize()]; readVCN(getStartVCN(), data, 0, nrClusters); AttributeListBlock listBlock = new AttributeListBlock(data, 0, getAttributeActualSize()); - return listBlock.getEntries(attrTypeID); + return listBlock.getAllEntries(); } - } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeRes.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeRes.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListAttributeRes.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -22,14 +22,14 @@ package org.jnode.fs.ntfs; import java.io.IOException; -import java.util.List; +import java.util.Iterator; /** * $ATTRIBUTE_LIST attribute, resident version. * * XXX: Is there a sensible way we can merge this with the non-resident version? * - * @author Daniel Noll (da...@nu...) + * @author Daniel Noll (da...@no...) */ final class AttributeListAttributeRes extends NTFSResidentAttribute implements AttributeListAttribute { @@ -43,20 +43,16 @@ } /** - * Gets an entry from the attribute list. + * Gets an iterator over all the entries in the attribute list. * - * XXX: What if there are multiple? In the case I've seen, there are multiple but only the first - * one contains any data. - * - * @param attrTypeID the type of attribute to find. - * @return the attribute entry. - * @throws IOException if there is an error reading the attribute's non-resident data. + * @return an iterator of all attribute list entries. + * @throws IOException if there is an error reading the attribute's data. */ - public List<AttributeListEntry> getEntries(int attrTypeID) throws IOException { + public Iterator<AttributeListEntry> getAllEntries() throws IOException { final byte[] data = new byte[getAttributeLength()]; getData(getAttributeOffset(), data, 0, data.length); AttributeListBlock listBlock = new AttributeListBlock(data, 0, getAttributeLength()); - return listBlock.getEntries(attrTypeID); + return listBlock.getAllEntries(); } } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListBlock.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -21,8 +21,8 @@ package org.jnode.fs.ntfs; -import java.util.ArrayList; -import java.util.List; +import java.util.Iterator; +import java.util.NoSuchElementException; /** * Data structure containing a list of {@link AttributeListEntry} entries. @@ -46,32 +46,71 @@ } /** - * Finds all entries from the attribute list with the given type ID. + * Gets an iterator over all the entries in the attribute list. * - * XXX: What if there are multiple? In the case I've seen, there are - * multiple but only the first one contains any data. - * - * @param attrTypeID the type of attribute to find. - * @return the attribute entry. + * @return an iterator of all attribute list entries. */ - public List<AttributeListEntry> getEntries(int attrTypeID) { - final List<AttributeListEntry> entries = new ArrayList<AttributeListEntry>(); - int offset = 0; - while (offset + 4 <= length) // Should be just (offset < length) but it seems we have some uneven lengths. - { - try { - int type = getUInt32AsInt(offset + 0x00); - if (type == attrTypeID) { - entries.add(new AttributeListEntry(this, offset)); - } + public Iterator<AttributeListEntry> getAllEntries() { + return new AttributeListEntryIterator(); + } - int length = getUInt16(offset + 0x04); - offset += length; - } catch (ArrayIndexOutOfBoundsException e) { - log.error("..."); + /** + * Iteration of attribute list entries. + */ + private class AttributeListEntryIterator implements Iterator<AttributeListEntry> { + + /** + * The next element to return. + */ + private AttributeListEntry nextElement; + + /** + * Current offset being looked at. + */ + private int offset = 0; + + /** + * Returns {@code true} if there are more elements in the iteration. + * + * @return {@code true} if there are more elements in the iteration. + */ + public boolean hasNext() { + // Safety check in case hasNext is called twice without calling next. + if (nextElement != null) { + return true; } + + // If the length is specified, use it to determine where the block ends. + if (offset + 4 > length) { + return false; + } + + int length = getUInt16(offset + 0x04); + nextElement = new AttributeListEntry(AttributeListBlock.this, offset); + offset += length; + return true; } - return entries; + + /** + * Gets the next entry from the iteration. + * + * @return the next entry from the iteration. + */ + public AttributeListEntry next() { + if (hasNext()) { + AttributeListEntry result = nextElement; + nextElement = null; + return result; + } else { + throw new NoSuchElementException("Iterator has no more entries"); + } + } + + /** + * @throws UnsupportedOperationException always. + */ + public void remove() { + throw new UnsupportedOperationException(); + } } - } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/AttributeListEntry.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -56,8 +56,33 @@ } /** + * Gets the length of the name. Not so useful for callers, hence private. + * @return the name length. + */ + private int getNameLength() { + return getUInt8(0x06); + } + + /** + * Gets the offset of the name. Not so useful for callers, hence private. + * @return the name offset (from the front of the entry.) + */ + private int getNameOffset() { + return getUInt8(0x07); + } + + /** + * Gets the starting VCN of the attribute, zero if the attribute is resident. + * @return the starting VCN. + */ + public int getStartingVCN() { + return getUInt16(0x08); + } + + /** * Gets the file reference number, which is the lowest 48 bits of the MFT - * reference. + * reference. This may point to the same file record which contains the + * attribute list. * * @return the file reference number. */ @@ -75,10 +100,46 @@ return getUInt48(0x16); } - // TODO: - // 0x06 1 Name length (N) - // 0x07 1 Offset to Name (a) - // 0x08 8 Starting VCN (b) - // 0x18 2 Attribute Id (c) - // 0x1A 2N Name in Unicode (if N >0) + /** + * Gets the ID of the attribute. This ID is unique within all attributes. + * @return the attribute ID. + */ + public int getAttributeID() { + return getUInt16(0x18); + } + + /** + * Gets the name of the attribute. Some attributes don't have names, and the names + * on attributes are supposedly unique within a given attribute type. + * + * @return the name of the attribute referenced by this entry. Returns the empty string + * if the attribute has no name. + */ + public String getName() { + final int nameLength = getNameLength(); + if (nameLength == 0) { + return ""; + } else { + char[] name = new char[nameLength]; + for (int i = 0, off = getNameOffset(); i < nameLength; i++, off += 2) { + name[i] = getChar16(off); + } + return new String(name); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(super.toString()); + builder.append("[type=").append(getType()); + builder.append(",name=").append(getName()); + if (getStartingVCN() == 0) { + builder.append(",resident"); + } else { + builder.append(",ref=").append(getFileReferenceNumber()); + builder.append(",vcn=").append(getStartingVCN()); + } + builder.append(",id=").append(getAttributeID()).append("]"); + return builder.toString(); + } } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/FileRecord.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -22,6 +22,8 @@ package org.jnode.fs.ntfs; import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import org.jnode.util.NumberUtils; @@ -34,28 +36,57 @@ */ class FileRecord extends NTFSRecord { + /** + * Sequence number of the file within the MFT. + */ + private long referenceNumber; + + /** + * Cached attribute list attribute. + */ + private AttributeListAttribute attributeListAttribute; + + /** + * Cached file name attribute. + */ private FileNameAttribute fileNameAttribute; /** * Initialize this instance. - * - * @param volume - * @param buffer + * + * @param volume reference to the NTFS volume. + * @param referenceNumber the reference number of the file within the MFT. + * @param buffer data buffer. + * @param offset offset into the buffer. */ - public FileRecord(NTFSVolume volume, byte[] buffer, int offset) throws IOException { + public FileRecord(NTFSVolume volume, long referenceNumber, byte[] buffer, int offset) throws IOException { super(volume, buffer, offset); + this.referenceNumber = referenceNumber; + + // Linux NTFS docs say there can only be one of these, so I'll believe them. + attributeListAttribute = (AttributeListAttribute) + findStoredAttributeByType(NTFSAttribute.Types.ATTRIBUTE_LIST); + // check for the magic number to see if we have a filerecord if (getMagic() != Magic.FILE) { throw new IOException("Invalid magic found: " + getMagic()); } + + // This additional sanity check is possible if the record also contains the MFT number. + // Helps catch bugs where a record is being read from the wrong offset. + final long storedReferenceNumber = getStoredReferenceNumber(); + if (storedReferenceNumber >= 0 && referenceNumber != storedReferenceNumber) { + throw new IOException("Stored reference number " + getStoredReferenceNumber() + + " does not match reference number " + referenceNumber); + } } /** * Gets the allocated size of the FILE record in bytes. * - * @return Returns the alocatedSize. + * @return Returns the allocated size. */ - public long getAlocatedSize() { + public long getAllocatedSize() { return getUInt32(0x1C); } @@ -69,12 +100,21 @@ } /** - * Is this a directory. + * Is this record in use? + * + * @return {@code true} if the record is in use. + */ + public boolean isInUse() { + return (getFlags() & 0x01) != 0; + } + + /** + * Is this a directory? * - * @return + * @return {@code true} if the record is a directory. */ public boolean isDirectory() { - return ((this.getFlags() & 0x02) != 0); + return (getFlags() & 0x02) != 0; } /** @@ -90,9 +130,9 @@ * Gets the byte offset to the first attribute in this mft record from the * start of the mft record. * - * @return Returns the firtAttributeOffset. + * @return the first attribute offset. */ - public int getFirtAttributeOffset() { + public int getFirstAttributeOffset() { return getUInt16(0x14); } @@ -131,6 +171,22 @@ } /** + * Gets the stored reference number. This can be compared against the reference number + * to confirm that the correct file record was returned, however it is not available + * on all versions of NTFS, and even on recent versions some MFT records lack it. + * + * @return the stored file reference number, or {@code -1} if it is not stored. + */ + public long getStoredReferenceNumber() { + // Expected to be 0x2A pre-XP. + if (getUpdateSequenceOffset() >= 0x30) { + return getUInt32(0x2C); + } else { + return -1; + } + } + + /** * Gets the name of this file. * * @return @@ -151,7 +207,7 @@ */ public FileNameAttribute getFileNameAttribute() { if (fileNameAttribute == null) { - fileNameAttribute = (FileNameAttribute) getAttribute(NTFSAttribute.Types.FILE_NAME); + fileNameAttribute = (FileNameAttribute) findAttributeByType(NTFSAttribute.Types.FILE_NAME); } return fileNameAttribute; } @@ -159,87 +215,133 @@ /** * Gets an attribute in this filerecord with a given id. * - * XXX: Returning an iterator of multiple might be better. + * @return an iteratover over attributes stored in this file record. + */ + public AttributeIterator getAllStoredAttributes() { + return new StoredAttributeIterator(); + } + + /** + * Finds a single stored attribute by ID. * - * @param attrTypeID the type ID of the attribute we're looking for. - * @return the attribute. + * @param id the ID. + * @return the attribute found, or {@code null} if not found. */ - public NTFSAttribute getAttribute(int attrTypeID) { - log.debug("getAttribute(0x" + NumberUtils.hex(attrTypeID, 4) + ")"); - int offset = this.getFirtAttributeOffset(); - while (true) { - final int type = getUInt32AsInt(offset + 0x00); - if (type == 0xFFFFFFFF) { - // The end of the attribute list - break; - } else if (type == attrTypeID) { - return NTFSAttribute.getAttribute(this, offset); - } else { - // Skip and go to the next - final int attrLength = getUInt32AsInt(offset + 0x04); - offset += attrLength; + private NTFSAttribute findStoredAttributeByID(int id) { + AttributeIterator iter = getAllStoredAttributes(); + NTFSAttribute attr; + while ((attr = iter.next()) != null) { + if (attr.getAttributeID() == id) { + return attr; } } + return null; + } - if (attrTypeID != NTFSAttribute.Types.ATTRIBUTE_LIST) { - final AttributeListAttribute attributeList = - (AttributeListAttribute) getAttribute(NTFSAttribute.Types.ATTRIBUTE_LIST); - if (attributeList != null) { - log.info("Has $ATTRIBUTE_LIST attribute"); + /** + * Finds a single stored attribute by type. + * + * @param typeID the type ID + * @return the attribute found, or {@code null} if not found. + * @see NTFSAttribute.Types + */ + private NTFSAttribute findStoredAttributeByType(int typeID) { + AttributeIterator iter = getAllStoredAttributes(); + NTFSAttribute attr; + while ((attr = iter.next()) != null) { + if (attr.getAttributeType() == typeID) { + return attr; + } + } + return null; + } - try { - final List<AttributeListEntry> entries = attributeList.getEntries(attrTypeID); - if (!entries.isEmpty()) { - log.debug("Found entries via $ATTRIBUTE_LIST: " + entries); - MasterFileTable mft = getVolume().getMFT(); - NTFSAttribute attribute = null; - for (AttributeListEntry entry : entries) { - // XXX: This is a little crappy as we should already know the exact offset - // of this attribute. This just makes the best of the API we already - // use everywhere else. - NTFSAttribute attr = - mft.getRecord(entry.getFileReferenceNumber()).getAttribute( - attrTypeID); + /** + * Gets an iterator over all attributes in this file record, including any attributes + * which are stored in other file records referenced from an $ATTRIBUTE_LIST attribute. + * + * @return an iterator over all attributes. + */ + private AttributeIterator getAllAttributes() { + if (attributeListAttribute == null) { + return getAllStoredAttributes(); + } else { + return new AttributeListAttributeIterator(); + } + } - if (attribute == null) { - // First attribute encountered. - attribute = attr; - if (!(attr instanceof NTFSNonResidentAttribute)) { - log - .info("Don't know how to glue together resident attributes, " - + "returning the first one alone"); - break; - } - } else { - // Subsequent attribute. - if (attr instanceof NTFSNonResidentAttribute) { - log.debug("Appending data runs onto parent attribute"); - ((NTFSNonResidentAttribute) attribute) - .appendDataRuns(((NTFSNonResidentAttribute) attr) - .getDataRuns()); - } else { - log.info("Don't know how to glue a resident attribute onto " - + "a non-resident one, skipping this attribute."); - } - } - } + /** + * Gets the first attribute in this filerecord with a given type. + * + * @param attrTypeID the type ID of the attribute we're looking for. + * @return the attribute. + */ + public NTFSAttribute findAttributeByType(int attrTypeID) { + log.debug("findAttributeByType(0x" + NumberUtils.hex(attrTypeID, 4) + ")"); - return attribute; - } - } catch (IOException e) { - log.error("IO error getting locating attribute list entry", e); - } + AttributeIterator iter = getAllAttributes(); + NTFSAttribute attr; + while ((attr = iter.next()) != null) { + if (attr.getAttributeType() == attrTypeID) { + log.debug("findAttributeByType(0x" + NumberUtils.hex(attrTypeID, 4) + ") found"); + return attr; } } - log.info("getAttribute(0x" + NumberUtils.hex(attrTypeID, 4) + ") not found"); + log.debug("findAttributeByType(0x" + NumberUtils.hex(attrTypeID, 4) + ") not found"); return null; } + /** + * Gets attributes in this filerecord with a given type and name. + * + * @param attrTypeID the type ID of the attribute we're looking for. + * @param name the name to look for. + * @return the attributes, will be empty if not found, never {@code null}. + */ + public AttributeIterator findAttributesByTypeAndName(final int attrTypeID, final String name) { + log.debug("findAttributesByTypeAndName(0x" + NumberUtils.hex(attrTypeID, 4) + + "," + name + ")"); + return new FilteredAttributeIterator(getAllAttributes()) { + @Override + protected boolean matches(NTFSAttribute attr) { + if (attr.getAttributeType() == attrTypeID) { + String attrName = attr.getAttributeName(); + if (name == null ? attrName == null : name.equals(attrName)) { + log.debug("findAttributesByTypeAndName(0x" + NumberUtils.hex(attrTypeID, 4) + + "," + name + ") found"); + return true; + } + } + return false; + } + }; + } + + /** + * Reads data from the file. + * + * @param fileOffset the offset into the file. + * @param dest the destination byte array into which to copy the file data. + * @param off the offset into the destination byte array. + * @param len the number of bytes of data to read. + * @throws IOException if an error occurs reading from the filesystem. + */ public void readData(long fileOffset, byte[] dest, int off, int len) throws IOException { - final NTFSAttribute data = this.getAttribute(NTFSAttribute.Types.DATA); - if (data.isResident()) { - final NTFSResidentAttribute resData = (NTFSResidentAttribute) data; + // 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); + NTFSAttribute attr = dataAttrs.next(); + if (attr == null) { + throw new IOException("Data attribute not found, file record = " + this); + } + + if (attr.isResident()) { + if (dataAttrs.next() != null) { + throw new IOException("Resident attribute should be by itself, file record = " + this); + } + + final NTFSResidentAttribute resData = (NTFSResidentAttribute) attr; final int attrLength = resData.getAttributeLength(); if (attrLength < len) { throw new IOException("File data(" + attrLength + @@ -252,12 +354,167 @@ // calculate start and end cluster final int clusterSize = getVolume().getClusterSize(); - final long startCluster = (fileOffset / clusterSize); - final int nrClusters = (int) ((len + (fileOffset % clusterSize)) / clusterSize) + 1; - final NTFSNonResidentAttribute nresData = (NTFSNonResidentAttribute) data; + final long startCluster = fileOffset / clusterSize; + final long endCluster = (fileOffset + len - 1) / clusterSize; + final int nrClusters = (int) (endCluster - startCluster + 1); + final byte[] tmp = new byte[nrClusters * clusterSize]; - final byte[] tmp = new byte[nrClusters * clusterSize]; - nresData.readVCN(startCluster, tmp, 0, nrClusters); + long clusterWithinNresData = startCluster; + int readClusters = 0; + do { + if (attr.isResident()) { + throw new IOException("Resident attribute should be by itself, file record = " + this); + } + + final NTFSNonResidentAttribute nresData = (NTFSNonResidentAttribute) attr; + + readClusters += nresData.readVCN(clusterWithinNresData, tmp, 0, nrClusters); + if (readClusters == nrClusters) { + // Already done. + break; + } + + // When there are multiple attributes, the data in each one claims to start at VCN 0. + // Clearly this is not the case, so we need to offset when we read. + clusterWithinNresData -= nresData.getNumberOfVCNs(); + attr = dataAttrs.next(); + } while (attr != null); + + if (readClusters != nrClusters) { + throw new IOException("Requested " + nrClusters + " clusters but only read " + readClusters); + } + System.arraycopy(tmp, (int) fileOffset % clusterSize, dest, off, len); } + + public String toString() { + if (isInUse()) { + return super.toString() + "[fileName=" + getFileName() + "]"; + } else { + return super.toString() + "[unused]"; + } + } + + /** + * Iterator over multiple attributes, where those attributes are stored in an + * attribute list instead of directly in the file record. + */ + private class AttributeListAttributeIterator extends AttributeIterator { + + /** + * Current iterator over attribute list entries. + */ + private Iterator<AttributeListEntry> entryIterator; + + /** + * Constructs the iterator. + */ + private AttributeListAttributeIterator() { + try { + entryIterator = attributeListAttribute.getAllEntries(); + } catch (IOException e) { + log.error("Error getting attributes from attribute list, file record " + FileRecord.this, e); + List<AttributeListEntry> emptyList = Collections.emptyList(); + entryIterator = emptyList.iterator(); + } + } + + @Override + protected NTFSAttribute next() { + while (entryIterator.hasNext()) { + AttributeListEntry entry = entryIterator.next(); + try { + // If it's resident (i.e. in the current file record) then we don't need to + // look it up, and doing so would risk infinite recursion. + FileRecord holdingRecord; + if (entry.getFileReferenceNumber() == referenceNumber) { + holdingRecord = FileRecord.this; + } else { + holdingRecord = getVolume().getMFT().getRecord(entry.getFileReferenceNumber()); + } + + return holdingRecord.findStoredAttributeByID(entry.getAttributeID()); + } catch (IOException e) { + // XXX: I wish the iterator could just throw this error out. Should we + // just create a different kind of iterator class instead? + log.error("Error getting MFT or FileRecord for attribute in list, ref = 0x" + + Long.toHexString(entry.getFileReferenceNumber()), e); + } + } + + return null; + } + } + + /** + * Iterator over stored attributes in this file record. + */ + private class StoredAttributeIterator extends AttributeIterator { + /** + * The next attribute offset to look at. + */ + private int nextOffset = getFirstAttributeOffset(); + + @Override + protected NTFSAttribute next() { + final int offset = nextOffset; + final int type = getUInt32AsInt(offset + 0x00); + if (type == 0xFFFFFFFF) { + // Normal end of list condition. + return null; + } else { + NTFSAttribute attribute = NTFSAttribute.getAttribute(FileRecord.this, offset); + int offsetToNextOffset = getUInt32AsInt(offset + 0x04); + if (offsetToNextOffset <= 0) { + log.error("Non-positive offset, preventing infinite loop. Data on disk may be corrupt. " + + "referenceNumber = " + referenceNumber); + return null; + } + + nextOffset += offsetToNextOffset; + return attribute; + } + } + } + + /** + * An iterator for filtering another iterator. + */ + private abstract class FilteredAttributeIterator extends AttributeIterator { + private AttributeIterator inner; + private FilteredAttributeIterator(AttributeIterator inner) { + this.inner = inner; + } + + @Override + protected NTFSAttribute next() { + NTFSAttribute attr; + while ((attr = inner.next()) != null) { + if (matches(attr)) { + return attr; + } + } + return null; + } + + /** + * Implemented by subclasses to perform matching logic. + * + * @param attr the attribute. + * @return {@code true} if it matches, {@code false} otherwise. + */ + protected abstract boolean matches(NTFSAttribute attr); + } + + /** + * Holds code common to both types of attribute list. + */ + private abstract class AttributeIterator { + /** + * Gets the next element from the iterator. + * + * @return the next element from the iterator. Returns {@code null} at the end. + */ + protected abstract NTFSAttribute next(); + } } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/IndexAllocationAttribute.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/IndexAllocationAttribute.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/IndexAllocationAttribute.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -47,9 +47,26 @@ public IndexBlock getIndexBlock(IndexRoot indexRoot, long vcn) throws IOException { log.debug("getIndexBlock(..," + vcn + ")"); final FileRecord fileRecord = getFileRecord(); - final int nrClusters = indexRoot.getClustersPerIndexBlock(); - final byte[] data = new byte[nrClusters * fileRecord.getVolume().getClusterSize()]; - readVCN(vcn, data, 0, nrClusters); - return new IndexBlock(fileRecord, data, 0); + + // VCN passed in is relative to the size of index clusters, not filesystem clusters. + // Calculate the actual offset we need in terms of filesystem clusters, + // and how many actual clusters we will need to read. + + final int indexBlockSize = indexRoot.getIndexBlockSize(); + final int indexClusterSize = indexBlockSize / indexRoot.getClustersPerIndexBlock(); + final int fsClusterSize = fileRecord.getVolume().getClusterSize(); + final long fsVcn = vcn * indexClusterSize / fsClusterSize; + final int fsNrClusters = (indexBlockSize - 1) / fsClusterSize + 1; + final int offsetIntoVcn = (int) ((vcn * indexClusterSize) % fsClusterSize); + + final byte[] data = new byte[fsNrClusters * fsClusterSize]; + final int readClusters = readVCN(fsVcn, data, 0, fsNrClusters); + if (readClusters != fsNrClusters) { + // If we don't throw an error now, it just fails more mysteriously later! + throw new IOException("Number of clusters read was not the number requested (requested " + + fsNrClusters + ", read " + readClusters + ")"); + } + + return new IndexBlock(fileRecord, data, offsetIntoVcn); } } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/IndexEntry.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/IndexEntry.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/IndexEntry.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -55,7 +55,7 @@ } public boolean hasSubNodes() { - return (getFlags() & 0x01) != 0; + return (getIndexFlags() & 0x01) != 0; } /** @@ -70,12 +70,12 @@ * Gets the flags of this index entry. * @return */ - public int getFlags() { + public int getIndexFlags() { return getUInt8(0x0C); } public boolean isLastIndexEntryInSubnode() { - return (getFlags() & 0x02) != 0; + return (getIndexFlags() & 0x02) != 0; } /** @@ -88,7 +88,7 @@ } public boolean isDirectory() { - return (getUInt32(0x48) & 0x10000000L) != 0; + return (getFileFlags() & 0x10000000L) != 0; } public String getFileName() { @@ -126,4 +126,12 @@ } return name; } + + @Override + public String toString() { + return super.toString() + + "[fileName=" + getFileName() + + ",indexFlags=" + getIndexFlags() + + ",fileFlags=" + getFileFlags() + "]"; + } } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/MasterFileTable.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/MasterFileTable.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/MasterFileTable.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -123,7 +123,7 @@ * @throws IOException */ public MasterFileTable(NTFSVolume volume, byte[] buffer, int offset) throws IOException { - super(volume, buffer, offset); + super(volume, SystemFiles.MFT, buffer, offset); } /** @@ -142,7 +142,7 @@ // read the buffer final byte[] buffer = new byte[bytesPerFileRecord]; readData(offset, buffer, 0, bytesPerFileRecord); - return new FileRecord(volume, buffer, 0); + return new FileRecord(volume, index, buffer, 0); } public FileRecord getIndexedFileRecord(IndexEntry indexEntry) throws IOException { Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSEntry.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSEntry.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSEntry.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -138,6 +138,7 @@ public FSDirectory getDirectory() throws IOException { if (this.isDirectory()) { if (cachedFSObject == null) { + // XXX: Why can't this just use getFileRecord()? cachedFSObject = new NTFSDirectory(fs, getFileRecord().getVolume().getMFT() .getIndexedFileRecord(indexEntry)); @@ -194,4 +195,9 @@ public boolean isDirty() throws IOException { return true; } + + @Override + public String toString() { + return super.toString() + '(' + indexEntry + ')'; + } } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSIndex.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSIndex.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSIndex.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -62,7 +62,7 @@ public IndexRootAttribute getIndexRootAttribute() { if (indexRootAttribute == null) { indexRootAttribute = (IndexRootAttribute) - fileRecord.getAttribute(NTFSAttribute.Types.INDEX_ROOT); + fileRecord.findAttributeByType(NTFSAttribute.Types.INDEX_ROOT); log.debug("getIndexRootAttribute: " + indexRootAttribute); } return indexRootAttribute; @@ -76,7 +76,7 @@ public IndexAllocationAttribute getIndexAllocationAttribute() { if (indexAllocationAttribute == null) { indexAllocationAttribute = (IndexAllocationAttribute) - fileRecord.getAttribute(NTFSAttribute.Types.INDEX_ALLOCATION); + fileRecord.findAttributeByType(NTFSAttribute.Types.INDEX_ALLOCATION); } return indexAllocationAttribute; } Modified: trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java =================================================================== --- trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java 2008-12-08 07:14:48 UTC (rev 4780) +++ trunk/fs/src/fs/org/jnode/fs/ntfs/NTFSNonResidentAttribute.java 2008-12-08 08:08:56 UTC (rev 4781) @@ -91,7 +91,7 @@ * * @return the size allocated to the attribute. */ - public long getAttributeAlocatedSize() { + public long getAttributeAllocatedSize() { return getUInt32(0x28); } @@ -125,11 +125,13 @@ } // check the dataruns final int clusterSize = getFileRecord().getVolume().getClusterSize(); - if (this.numberOfVCNs != this.getAttributeAlocatedSize() / clusterSize) { - log.error("ERROR: The number of VCNs from the data runs is different than the allocated size!: - " + - this.numberOfVCNs); - log.error("Alocatedsize = " + getAttributeAlocatedSize() / clusterSize); - log.error("number of data runs = " + dataRuns.size()); + // Rounds up but won't work for 0, which shouldn't occur here. + final long allocatedVCNs = (getAttributeAllocatedSize() - 1) / clusterSize + 1; + if (this.numberOfVCNs != allocatedVCNs) { + // Probably not a problem, often multiple attributes make up one allocation. + log.debug("VCN mismatch between data runs and allocated size, possibly a composite attribute. " + + "data run VCNs = " + this.numberOfVCNs + ", allocated size = " + allocatedVCNs + + ", data run count = " + dataRuns.size()); } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |