1 /*
2  * Copyright (C) 2009,2010 Matthias Treydte <mt@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      */
ClusterChain(Fat fat, boolean readOnly)46     public ClusterChain(Fat fat, boolean readOnly) {
47         this(fat, 0, readOnly);
48     }
49 
ClusterChain(Fat fat, long startCluster, boolean readOnly)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 
getClusterSize()69     public int getClusterSize() {
70         return clusterSize;
71     }
72 
getFat()73     public Fat getFat() {
74         return fat;
75     }
76 
getDevice()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      */
getStartCluster()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      */
getDevOffset(long cluster, int clusterOffset)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      */
getLengthOnDisk()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      */
setSize(long size)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      */
getChainLength()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      */
setChainLength(int nrClusters)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 
readData(long offset, ByteBuffer dest)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      */
writeData(long offset, ByteBuffer srcBuf)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
equals(Object obj)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
hashCode()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