Home | History | Annotate | Download | only in fat
      1 /*
      2  * Copyright (C) 2009,2010 Matthias Treydte <mt (at) waldheinz.de>
      3  *
      4  * This library is free software; you can redistribute it and/or modify it
      5  * under the terms of the GNU Lesser General Public License as published
      6  * by the Free Software Foundation; either version 2.1 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful, but
     10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
     12  * License for more details.
     13  *
     14  * You should have received a copy of the GNU Lesser General Public License
     15  * along with this library; If not, write to the Free Software Foundation, Inc.,
     16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     17  */
     18 
     19 package de.waldheinz.fs.fat;
     20 
     21 import de.waldheinz.fs.AbstractFsObject;
     22 import de.waldheinz.fs.BlockDevice;
     23 import java.io.EOFException;
     24 import java.io.IOException;
     25 import java.nio.ByteBuffer;
     26 
     27 /**
     28  * A chain of clusters as stored in a {@link Fat}.
     29  *
     30  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
     31  */
     32 final class ClusterChain extends AbstractFsObject {
     33     protected final Fat fat;
     34     private final BlockDevice device;
     35     private final int clusterSize;
     36     protected final long dataOffset;
     37 
     38     private long startCluster;
     39 
     40     /**
     41      * Creates a new {@code ClusterChain} that contains no clusters.
     42      *
     43      * @param fat the {@code Fat} that holds the new chain
     44      * @param readOnly if the chain should be created read-only
     45      */
     46     public ClusterChain(Fat fat, boolean readOnly) {
     47         this(fat, 0, readOnly);
     48     }
     49 
     50     public ClusterChain(Fat fat, long startCluster, boolean readOnly) {
     51         super(readOnly);
     52 
     53         this.fat = fat;
     54 
     55         if (startCluster != 0) {
     56             this.fat.testCluster(startCluster);
     57 
     58             if (this.fat.isFreeCluster(startCluster))
     59                 throw new IllegalArgumentException(
     60                     "cluster " + startCluster + " is free");
     61         }
     62 
     63         this.device = fat.getDevice();
     64         this.dataOffset = FatUtils.getFilesOffset(fat.getBootSector());
     65         this.startCluster = startCluster;
     66         this.clusterSize = fat.getBootSector().getBytesPerCluster();
     67     }
     68 
     69     public int getClusterSize() {
     70         return clusterSize;
     71     }
     72 
     73     public Fat getFat() {
     74         return fat;
     75     }
     76 
     77     public BlockDevice getDevice() {
     78         return device;
     79     }
     80 
     81     /**
     82      * Returns the first cluster of this chain.
     83      *
     84      * @return the chain's first cluster, which may be 0 if this chain does
     85      *      not contain any clusters
     86      */
     87     public long getStartCluster() {
     88         return startCluster;
     89     }
     90 
     91     /**
     92      * Calculates the device offset (0-based) for the given cluster and offset
     93      * within the cluster.
     94      *
     95      * @param cluster
     96      * @param clusterOffset
     97      * @return long
     98      * @throws FileSystemException
     99      */
    100     private long getDevOffset(long cluster, int clusterOffset) {
    101         return dataOffset + clusterOffset +
    102                 ((cluster - Fat.FIRST_CLUSTER) * clusterSize);
    103     }
    104 
    105     /**
    106      * Returns the size this {@code ClusterChain} occupies on the device.
    107      *
    108      * @return the size this chain occupies on the device in bytes
    109      */
    110     public long getLengthOnDisk() {
    111         if (getStartCluster() == 0) return 0;
    112 
    113         return getChainLength() * clusterSize;
    114     }
    115 
    116     /**
    117      * Sets the length of this {@code ClusterChain} in bytes. Because a
    118      * {@code ClusterChain} can only contain full clusters, the new size
    119      * will always be a multiple of the cluster size.
    120      *
    121      * @param size the desired number of bytes the can be stored in
    122      *      this {@code ClusterChain}
    123      * @return the true number of bytes this {@code ClusterChain} can contain
    124      * @throws IOException on error setting the new size
    125      * @see #setChainLength(int)
    126      */
    127     public long setSize(long size) throws IOException {
    128         final long nrClusters = ((size + clusterSize - 1) / clusterSize);
    129         if (nrClusters > Integer.MAX_VALUE)
    130             throw new IOException("too many clusters");
    131 
    132         setChainLength((int) nrClusters);
    133 
    134         return clusterSize * nrClusters;
    135     }
    136 
    137     /**
    138      * Determines the length of this {@code ClusterChain} in clusters.
    139      *
    140      * @return the length of this chain
    141      */
    142     public int getChainLength() {
    143         if (getStartCluster() == 0) return 0;
    144 
    145         final long[] chain = getFat().getChain(getStartCluster());
    146         return chain.length;
    147     }
    148 
    149     /**
    150      * Sets the length of this cluster chain in clusters.
    151      *
    152      * @param nrClusters the new number of clusters this chain should contain,
    153      *      must be {@code >= 0}
    154      * @throws IOException on error updating the chain length
    155      * @see #setSize(long)
    156      */
    157     public void setChainLength(int nrClusters) throws IOException {
    158         if (nrClusters < 0) throw new IllegalArgumentException(
    159                 "negative cluster count"); //NOI18N
    160 
    161         if ((this.startCluster == 0) && (nrClusters == 0)) {
    162             /* nothing to do */
    163         } else if ((this.startCluster == 0) && (nrClusters > 0)) {
    164             final long[] chain = fat.allocNew(nrClusters);
    165             this.startCluster = chain[0];
    166         } else {
    167             final long[] chain = fat.getChain(startCluster);
    168 
    169             if (nrClusters != chain.length) {
    170                 if (nrClusters > chain.length) {
    171                     /* grow the chain */
    172                     int count = nrClusters - chain.length;
    173 
    174                     while (count > 0) {
    175                         fat.allocAppend(getStartCluster());
    176                         count--;
    177                     }
    178                 } else {
    179                     /* shrink the chain */
    180                     if (nrClusters > 0) {
    181                         fat.setEof(chain[nrClusters - 1]);
    182                         for (int i = nrClusters; i < chain.length; i++) {
    183                             fat.setFree(chain[i]);
    184                         }
    185                     } else {
    186                         for (int i=0; i < chain.length; i++) {
    187                             fat.setFree(chain[i]);
    188                         }
    189 
    190                         this.startCluster = 0;
    191                     }
    192                 }
    193             }
    194         }
    195     }
    196 
    197     public void readData(long offset, ByteBuffer dest)
    198             throws IOException {
    199 
    200         int len = dest.remaining();
    201 
    202         if ((startCluster == 0 && len > 0)) throw new EOFException();
    203 
    204         final long[] chain = getFat().getChain(startCluster);
    205         final BlockDevice dev = getDevice();
    206 
    207         int chainIdx = (int) (offset / clusterSize);
    208         if (offset % clusterSize != 0) {
    209             int clusOfs = (int) (offset % clusterSize);
    210             int size = Math.min(len,
    211                     (int) (clusterSize - (offset % clusterSize) - 1));
    212             dest.limit(dest.position() + size);
    213 
    214             dev.read(getDevOffset(chain[chainIdx], clusOfs), dest);
    215 
    216             offset += size;
    217             len -= size;
    218             chainIdx++;
    219         }
    220 
    221         while (len > 0) {
    222             int size = Math.min(clusterSize, len);
    223             dest.limit(dest.position() + size);
    224 
    225             dev.read(getDevOffset(chain[chainIdx], 0), dest);
    226 
    227             len -= size;
    228             chainIdx++;
    229         }
    230     }
    231 
    232     /**
    233      * Writes data to this cluster chain, possibly growing the chain so it
    234      * can store the additional data. When this method returns without throwing
    235      * an exception, the buffer's {@link ByteBuffer#position() position} will
    236      * equal it's {@link ByteBuffer#limit() limit}, and the limit will not
    237      * have changed. This is not guaranteed if writing fails.
    238      *
    239      * @param offset the offset where to write the first byte from the buffer
    240      * @param srcBuf the buffer to write to this {@code ClusterChain}
    241      * @throws IOException on write error
    242      */
    243     public void writeData(long offset, ByteBuffer srcBuf) throws IOException {
    244 
    245         int len = srcBuf.remaining();
    246 
    247         if (len == 0) return;
    248 
    249         final long minSize = offset + len;
    250         if (getLengthOnDisk() < minSize) {
    251             setSize(minSize);
    252         }
    253 
    254         final long[] chain = fat.getChain(getStartCluster());
    255 
    256         int chainIdx = (int) (offset / clusterSize);
    257         if (offset % clusterSize != 0) {
    258             int clusOfs = (int) (offset % clusterSize);
    259             int size = Math.min(len,
    260                     (int) (clusterSize - (offset % clusterSize)));
    261             srcBuf.limit(srcBuf.position() + size);
    262 
    263             device.write(getDevOffset(chain[chainIdx], clusOfs), srcBuf);
    264 
    265             offset += size;
    266             len -= size;
    267             chainIdx++;
    268         }
    269 
    270         while (len > 0) {
    271             int size = Math.min(clusterSize, len);
    272             srcBuf.limit(srcBuf.position() + size);
    273 
    274             device.write(getDevOffset(chain[chainIdx], 0), srcBuf);
    275 
    276             len -= size;
    277             chainIdx++;
    278         }
    279 
    280     }
    281 
    282     @Override
    283     public boolean equals(Object obj) {
    284         if (obj == null) return false;
    285         if (!(obj instanceof ClusterChain)) return false;
    286 
    287         final ClusterChain other = (ClusterChain) obj;
    288 
    289         if (this.fat != other.fat &&
    290                 (this.fat == null || !this.fat.equals(other.fat))) {
    291 
    292             return false;
    293         }
    294 
    295         if (this.startCluster != other.startCluster) {
    296             return false;
    297         }
    298 
    299         return true;
    300     }
    301 
    302     @Override
    303     public int hashCode() {
    304         int hash = 3;
    305         hash = 79 * hash +
    306                 (this.fat != null ? this.fat.hashCode() : 0);
    307         hash = 79 * hash +
    308                 (int) (this.startCluster ^ (this.startCluster >>> 32));
    309         return hash;
    310     }
    311 
    312 }
    313