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 <waldheinz at gmail.com> 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