/* * $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.ntfs; import java.io.IOException; import java.util.Arrays; import org.apache.log4j.Logger; import org.jnode.fs.ntfs.attribute.NTFSNonResidentAttribute; /** * @author Ewout Prangsma (epr@users.sourceforge.net) */ public final class DataRun implements DataRunInterface { /** * Type of this datarun */ /** * logger */ protected static final Logger log = Logger.getLogger(DataRun.class); /** * Cluster number of first cluster of this run. If this is zero, the run * isn't actually stored as it is all zero. */ private final long cluster; /** * 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; /** * First VCN of this datarun. */ private long vcn; /** * Initialize this instance. * * @param cluster Cluster number of first cluster of this run. * @param length Length of datarun in clusters * @param sparse Flag indicating that the data is not stored on disk but is all zero. * @param size Size in bytes of this datarun descriptor * @param vcn First VCN of this datarun. */ public DataRun(long cluster, int length, boolean sparse, int size, long vcn) { this.cluster = cluster; this.length = length; this.sparse = sparse; this.size = size; this.vcn = vcn; } /** * Initialize this instance. * * @param attr * @param offset * @param vcn First VCN of this datarun. * @param previousLCN */ public DataRun(NTFSNonResidentAttribute attr, int offset, long vcn, long previousLCN) { NTFSStructure dataRunStructure = new NTFSStructure(attr, offset); // read first byte in type attribute int type = dataRunStructure.getUInt8(0); final int lenlen = type & 0xF; final int clusterlen = type >>> 4; this.size = lenlen + clusterlen + 1; this.vcn = vcn; switch (lenlen) { case 0x00: length = 0; break; case 0x01: length = dataRunStructure.getUInt8(1); break; case 0x02: length = dataRunStructure.getUInt16(1); break; case 0x03: length = dataRunStructure.getUInt24(1); break; case 0x04: length = dataRunStructure.getUInt32AsInt(1); break; default: throw new IllegalArgumentException("Invalid length length " + lenlen); } final int cluster; switch (clusterlen) { case 0x00: sparse = true; cluster = 0; break; case 0x01: cluster = dataRunStructure.getInt8(1 + lenlen); break; case 0x02: cluster = dataRunStructure.getInt16(1 + lenlen); break; case 0x03: cluster = dataRunStructure.getInt24(1 + lenlen); break; case 0x04: cluster = dataRunStructure.getInt32(1 + lenlen); break; default: throw new IllegalArgumentException("Unknown cluster length " + clusterlen); } this.cluster = cluster == 0 ? 0 : cluster + previousLCN; } /** * 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() { return this.cluster; } /** * Gets the size of this datarun descriptor in bytes. * * @return Returns the size. */ public int getSize() { return this.size; } /** * Gets the length of this datarun in clusters. * * @return Returns the length. */ public int getLength() { return length; } /** * Gets the first VCN of this data run. * * @return Returns the vcn. */ @Override public long getFirstVcn() { return this.vcn; } /** * Gets the last VCN of this data run. * * @return Returns the vcn. */ @Override public long getLastVcn() { return getFirstVcn() + getLength() - 1; } /** * Read clusters from this datarun. * * @param vcn * @param dst * @param dstOffset * @param nrClusters * @param clusterSize * @param volume * @return The number of clusters read. * @throws IOException */ public int readClusters(long vcn, byte[] dst, int dstOffset, int nrClusters, int clusterSize, NTFSVolume volume) throws IOException { final long myFirstVcn = getFirstVcn(); final int myLength = getLength(); final long myLastVcn = getLastVcn(); final long reqLastVcn = vcn + nrClusters - 1; if (log.isDebugEnabled()) { log.debug("me:" + myFirstVcn + "-" + myLastVcn + ", req:" + vcn + "-" + reqLastVcn); } if ((vcn > myLastVcn) || (myFirstVcn > reqLastVcn)) { // Not my region return 0; } final long actCluster; // Starting cluster final int count; // #clusters to read final int actDstOffset; // Actual dst offset if (vcn < myFirstVcn) { final int vcnDelta = (int) (myFirstVcn - vcn); count = Math.min(nrClusters - vcnDelta, myLength); actDstOffset = dstOffset + (vcnDelta * clusterSize); actCluster = getCluster(); } else { // vcn >= myFirstVcn final int vcnDelta = (int) (vcn - myFirstVcn); count = Math.min(nrClusters, myLength - vcnDelta); actDstOffset = dstOffset; actCluster = getCluster() + vcnDelta; } if (log.isDebugEnabled()) { log.debug("cluster=" + cluster + ", length=" + length + ", dstOffset=" + dstOffset); log.debug("cnt=" + count + ", actclu=" + actCluster + ", actdstoff=" + actDstOffset); } // Zero the area Arrays.fill(dst, actDstOffset, actDstOffset + count * clusterSize, (byte) 0); if (!isSparse()) { volume.readClusters(actCluster, dst, actDstOffset, count); } return count; } /** * Maps a virtual cluster to a logical cluster. * * @param vcn the virtual cluster number to map. * @return the logical cluster number or -1 if this cluster is not stored (e.g. for a sparse cluster). * @throws ArrayIndexOutOfBoundsException if the VCN doesn't belong to this data run. */ public long mapVcnToLcn(long vcn) { long myLastVcn = getFirstVcn() + getLength() - 1; if ((vcn > myLastVcn) || (getFirstVcn() > vcn)) { throw new ArrayIndexOutOfBoundsException("Invalid VCN for this data run: " + vcn); } long cluster = getCluster(); if (cluster == 0 || isSparse()) { // This is a sparse cluster, not actually stored on disk return -1; } final int vcnDelta = (int) (vcn - getFirstVcn()); return cluster + vcnDelta; } @Override public String toString() { return String.format("[%s-run vcn:%d-%d cluster:%d]", isSparse() ? "sparse" : "data", getFirstVcn(), getLastVcn(), getCluster()); } }