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.BlockDevice; 22 import java.io.IOException; 23 import java.util.Random; 24 25 /** 26 * <p> 27 * Allows to create FAT file systems on {@link BlockDevice}s which follow the 28 * "super floppy" standard. This means that the device will be formatted so 29 * that it does not contain a partition table. Instead, the entire device holds 30 * a single FAT file system. 31 * </p><p> 32 * This class follows the "builder" pattern, which means it's methods always 33 * returns the {@code SuperFloppyFormatter} instance they're called on. This 34 * allows to chain the method calls like this: 35 * <pre> 36 * BlockDevice dev = new RamDisk(16700000); 37 * FatFileSystem fs = SuperFloppyFormatter.get(dev). 38 * setFatType(FatType.FAT12).format(); 39 * </pre> 40 * 41 * </p> 42 * 43 * @author Matthias Treydte <matthias.treydte at meetwise.com> 44 */ 45 public final class SuperFloppyFormatter { 46 47 /** 48 * The media descriptor used (hard disk). 49 */ 50 public final static int MEDIUM_DESCRIPTOR_HD = 0xf8; 51 52 /** 53 * The default number of FATs. 54 */ 55 public final static int DEFAULT_FAT_COUNT = 2; 56 57 /** 58 * The default number of sectors per track. 59 */ 60 public final static int DEFAULT_SECTORS_PER_TRACK = 32; 61 62 /** 63 * The default number of heads. 64 * 65 * @since 0.6 66 */ 67 public final static int DEFAULT_HEADS = 64; 68 69 /** 70 * The default number of heads. 71 * 72 * @deprecated the name of this constant was mistyped 73 * @see #DEFAULT_HEADS 74 */ 75 @Deprecated 76 public final static int DEFULT_HEADS = DEFAULT_HEADS; 77 78 /** 79 * The default OEM name for file systems created by this class. 80 */ 81 public final static String DEFAULT_OEM_NAME = "fat32lib"; //NOI18N 82 83 private static final int MAX_DIRECTORY = 512; 84 85 private final BlockDevice device; 86 87 private String label; 88 private String oemName; 89 private FatType fatType; 90 private int sectorsPerCluster; 91 private int reservedSectors; 92 private int fatCount; 93 94 /** 95 * Creates a new {@code SuperFloppyFormatter} for the specified 96 * {@code BlockDevice}. 97 * 98 * @param device 99 * @throws IOException on error accessing the specified {@code device} 100 */ SuperFloppyFormatter(BlockDevice device)101 private SuperFloppyFormatter(BlockDevice device) throws IOException { 102 this.device = device; 103 this.oemName = DEFAULT_OEM_NAME; 104 this.fatCount = DEFAULT_FAT_COUNT; 105 setFatType(fatTypeFromDevice()); 106 } 107 108 /** 109 * Retruns a {@code SuperFloppyFormatter} instance suitable for formatting 110 * the specified device. 111 * 112 * @param dev the device that should be formatted 113 * @return the formatter for the device 114 * @throws IOException on error creating the formatter 115 */ get(BlockDevice dev)116 public static SuperFloppyFormatter get(BlockDevice dev) throws IOException { 117 return new SuperFloppyFormatter(dev); 118 } 119 120 /** 121 * Returns the OEM name that will be written to the {@link BootSector}. 122 * 123 * @return the OEM name of the new file system 124 */ getOemName()125 public String getOemName() { 126 return oemName; 127 } 128 129 /** 130 * Sets the OEM name of the boot sector. 131 * 132 * TODO: throw an exception early if name is invalid (too long, ...) 133 * 134 * @param oemName the new OEM name 135 * @return this {@code SuperFloppyFormatter} 136 * @see BootSector#setOemName(java.lang.String) 137 */ setOemName(String oemName)138 public SuperFloppyFormatter setOemName(String oemName) { 139 this.oemName = oemName; 140 return this; 141 } 142 143 /** 144 * Sets the volume label of the file system to create. 145 * 146 * TODO: throw an exception early if label is invalid (too long, ...) 147 * 148 * @param label the new file system label, may be {@code null} 149 * @return this {@code SuperFloppyFormatter} 150 * @see FatFileSystem#setVolumeLabel(java.lang.String) 151 */ setVolumeLabel(String label)152 public SuperFloppyFormatter setVolumeLabel(String label) { 153 this.label = label; 154 return this; 155 } 156 157 /** 158 * Returns the volume label that will be given to the new file system. 159 * 160 * @return the file system label, may be {@code null} 161 * @see FatFileSystem#getVolumeLabel() 162 */ getVolumeLabel()163 public String getVolumeLabel() { 164 return label; 165 } 166 initBootSector(BootSector bs)167 private void initBootSector(BootSector bs) 168 throws IOException { 169 170 bs.init(); 171 bs.setFileSystemTypeLabel(fatType.getLabel()); 172 bs.setNrReservedSectors(reservedSectors); 173 bs.setNrFats(fatCount); 174 bs.setSectorsPerCluster(sectorsPerCluster); 175 bs.setMediumDescriptor(MEDIUM_DESCRIPTOR_HD); 176 bs.setSectorsPerTrack(DEFAULT_SECTORS_PER_TRACK); 177 bs.setNrHeads(DEFAULT_HEADS); 178 bs.setOemName(oemName); 179 } 180 181 /** 182 * Initializes the boot sector and file system for the device. The file 183 * system created by this method will always be in read-write mode. 184 * 185 * @return the file system that was created 186 * @throws IOException on write error 187 */ format()188 public FatFileSystem format() throws IOException { 189 final int sectorSize = device.getSectorSize(); 190 final int totalSectors = (int)(device.getSize() / sectorSize); 191 192 final FsInfoSector fsi; 193 final BootSector bs; 194 if (sectorsPerCluster == 0) throw new AssertionError(); 195 196 if (fatType == FatType.FAT32) { 197 bs = new Fat32BootSector(device); 198 initBootSector(bs); 199 200 final Fat32BootSector f32bs = (Fat32BootSector) bs; 201 202 f32bs.setFsInfoSectorNr(1); 203 204 f32bs.setSectorsPerFat(sectorsPerFat(0, totalSectors)); 205 final Random rnd = new Random(System.currentTimeMillis()); 206 f32bs.setFileSystemId(rnd.nextInt()); 207 208 f32bs.setVolumeLabel(label); 209 210 /* create FS info sector */ 211 fsi = FsInfoSector.create(f32bs); 212 } else { 213 bs = new Fat16BootSector(device); 214 initBootSector(bs); 215 216 final Fat16BootSector f16bs = (Fat16BootSector) bs; 217 218 final int rootDirEntries = rootDirectorySize( 219 device.getSectorSize(), totalSectors); 220 221 f16bs.setRootDirEntryCount(rootDirEntries); 222 f16bs.setSectorsPerFat(sectorsPerFat(rootDirEntries, totalSectors)); 223 if (label != null) f16bs.setVolumeLabel(label); 224 fsi = null; 225 } 226 227 228 // bs.write(); 229 230 if (fatType == FatType.FAT32) { 231 Fat32BootSector f32bs = (Fat32BootSector) bs; 232 /* possibly writes the boot sector copy */ 233 f32bs.writeCopy(device); 234 } 235 236 final Fat fat = Fat.create(bs, 0); 237 238 final AbstractDirectory rootDirStore; 239 if (fatType == FatType.FAT32) { 240 rootDirStore = ClusterChainDirectory.createRoot(fat); 241 fsi.setFreeClusterCount(fat.getFreeClusterCount()); 242 fsi.setLastAllocatedCluster(fat.getLastAllocatedCluster()); 243 fsi.write(); 244 } else { 245 rootDirStore = Fat16RootDirectory.create((Fat16BootSector) bs); 246 } 247 248 final FatLfnDirectory rootDir = 249 new FatLfnDirectory(rootDirStore, fat, false); 250 251 rootDir.flush(); 252 253 for (int i = 0; i < bs.getNrFats(); i++) { 254 fat.writeCopy(FatUtils.getFatOffset(bs, i)); 255 } 256 257 bs.write(); 258 259 FatFileSystem fs = FatFileSystem.read(device, false); 260 261 if (label != null) { 262 fs.setVolumeLabel(label); 263 } 264 265 fs.flush(); 266 return fs; 267 } 268 sectorsPerFat(int rootDirEntries, int totalSectors)269 private int sectorsPerFat(int rootDirEntries, int totalSectors) 270 throws IOException { 271 272 final int bps = device.getSectorSize(); 273 final int rootDirSectors = 274 ((rootDirEntries * 32) + (bps - 1)) / bps; 275 final long tmp1 = 276 totalSectors - (this.reservedSectors + rootDirSectors); 277 int tmp2 = (256 * this.sectorsPerCluster) + this.fatCount; 278 279 if (fatType == FatType.FAT32) 280 tmp2 /= 2; 281 282 final int result = (int) ((tmp1 + (tmp2 - 1)) / tmp2); 283 284 return result; 285 } 286 287 /** 288 * Determines a usable FAT type from the {@link #device} by looking at the 289 * {@link BlockDevice#getSize() device size} only. 290 * 291 * @return the suggested FAT type 292 * @throws IOException on error determining the device's size 293 */ fatTypeFromDevice()294 private FatType fatTypeFromDevice() throws IOException { 295 return fatTypeFromSize(device.getSize()); 296 } 297 298 /** 299 * Determines a usable FAT type from the {@link #device} by looking at the 300 * {@link BlockDevice#getSize() device size} only. 301 * 302 * @return the suggested FAT type 303 * @throws IOException on error determining the device's size 304 */ fatTypeFromSize(long sizeInBytes)305 public static FatType fatTypeFromSize(long sizeInBytes) { 306 final long sizeInMb = sizeInBytes / (1024 * 1024); 307 if (sizeInMb < 4) return FatType.FAT12; 308 else if (sizeInMb < 512) return FatType.FAT16; 309 else return FatType.FAT32; 310 } 311 clusterSizeFromSize(long sizeInBytes, int sectorSize)312 public static int clusterSizeFromSize(long sizeInBytes, int sectorSize){ 313 switch(fatTypeFromSize(sizeInBytes)) { 314 case FAT12: 315 return sectorsPerCluster12(sizeInBytes, sectorSize); 316 case FAT16: 317 return sectorsPerCluster16FromSize(sizeInBytes, sectorSize); 318 case FAT32: 319 return sectorsPerCluster32FromSize(sizeInBytes, sectorSize); 320 321 default: 322 throw new AssertionError(); 323 } 324 } 325 326 /** 327 * Returns the exact type of FAT the will be created by this formatter. 328 * 329 * @return the FAT type 330 */ getFatType()331 public FatType getFatType() { 332 return this.fatType; 333 } 334 335 /** 336 * Sets the type of FAT that will be created by this 337 * {@code SuperFloppyFormatter}. 338 * 339 * @param fatType the desired {@code FatType} 340 * @return this {@code SuperFloppyFormatter} 341 * @throws IOException on error setting the {@code fatType} 342 * @throws IllegalArgumentException if {@code fatType} does not support the 343 * size of the device 344 */ setFatType(FatType fatType)345 public SuperFloppyFormatter setFatType(FatType fatType) 346 throws IOException, IllegalArgumentException { 347 348 if (fatType == null) throw new NullPointerException(); 349 350 switch (fatType) { 351 case FAT12: case FAT16: 352 this.reservedSectors = 1; 353 break; 354 355 case FAT32: 356 this.reservedSectors = 32; 357 } 358 359 this.sectorsPerCluster = defaultSectorsPerCluster(fatType); 360 this.fatType = fatType; 361 362 return this; 363 } 364 rootDirectorySize(int bps, int nbTotalSectors)365 private static int rootDirectorySize(int bps, int nbTotalSectors) { 366 final int totalSize = bps * nbTotalSectors; 367 if (totalSize >= MAX_DIRECTORY * 5 * 32) { 368 return MAX_DIRECTORY; 369 } else { 370 return totalSize / (5 * 32); 371 } 372 } 373 374 static private int MAX_FAT32_CLUSTERS = 0x0FFFFFF5; 375 sectorsPerCluster32FromSize(long size, int sectorSize)376 static private int sectorsPerCluster32FromSize(long size, int sectorSize) { 377 final long sectors = size / sectorSize; 378 379 if (sectors <= 66600) throw new IllegalArgumentException( 380 "disk too small for FAT32"); 381 382 return 383 sectors > 67108864 ? 64 : 384 sectors > 33554432 ? 32 : 385 sectors > 16777216 ? 16 : 386 sectors > 532480 ? 8 : 1; 387 } 388 sectorsPerCluster32()389 private int sectorsPerCluster32() throws IOException { 390 if (this.reservedSectors != 32) throw new IllegalStateException( 391 "number of reserved sectors must be 32"); 392 393 if (this.fatCount != 2) throw new IllegalStateException( 394 "number of FATs must be 2"); 395 396 final long sectors = device.getSize() / device.getSectorSize(); 397 398 if (sectors <= 66600) throw new IllegalArgumentException( 399 "disk too small for FAT32"); 400 401 return sectorsPerCluster32FromSize(device.getSize(), device.getSectorSize()); 402 } 403 404 static private int MAX_FAT16_CLUSTERS = 65524; 405 sectorsPerCluster16FromSize(long size, int sectorSize)406 static private int sectorsPerCluster16FromSize(long size, int sectorSize) { 407 final long sectors = size / sectorSize; 408 409 if (sectors <= 8400) throw new IllegalArgumentException( 410 "disk too small for FAT16"); 411 412 if (sectors > 4194304) throw new IllegalArgumentException( 413 "disk too large for FAT16"); 414 415 return 416 sectors > 2097152 ? 64 : 417 sectors > 1048576 ? 32 : 418 sectors > 524288 ? 16 : 419 sectors > 262144 ? 8 : 420 sectors > 32680 ? 4 : 2; 421 } 422 sectorsPerCluster16()423 private int sectorsPerCluster16() throws IOException { 424 if (this.reservedSectors != 1) throw new IllegalStateException( 425 "number of reserved sectors must be 1"); 426 427 if (this.fatCount != 2) throw new IllegalStateException( 428 "number of FATs must be 2"); 429 430 long size = device.getSize(); 431 int sectorSize = device.getSectorSize(); 432 return sectorsPerCluster16FromSize(size, sectorSize); 433 } 434 defaultSectorsPerCluster(FatType fatType)435 private int defaultSectorsPerCluster(FatType fatType) throws IOException { 436 long size = device.getSize(); 437 int sectorSize = device.getSectorSize(); 438 439 switch (fatType) { 440 case FAT12: 441 return sectorsPerCluster12(size, sectorSize); 442 443 case FAT16: 444 return sectorsPerCluster16(); 445 446 case FAT32: 447 return sectorsPerCluster32(); 448 449 default: 450 throw new AssertionError(); 451 } 452 } 453 sectorsPerCluster12(long size, int sectorSize)454 static private int sectorsPerCluster12(long size, int sectorSize) { 455 int result = 1; 456 457 final long sectors = size / sectorSize; 458 459 while (sectors / result > Fat16BootSector.MAX_FAT12_CLUSTERS) { 460 result *= 2; 461 if (result * size > 4096) throw new 462 IllegalArgumentException("disk too large for FAT12"); 463 } 464 465 return result; 466 } 467 468 } 469