diff options
Diffstat (limited to 'src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java')
-rw-r--r-- | src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java b/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java new file mode 100644 index 0000000..5a64639 --- /dev/null +++ b/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de> + * + * 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 de.waldheinz.fs.fat; + +import de.waldheinz.fs.BlockDevice; +import java.io.IOException; +import java.util.Random; + +/** + * <p> + * Allows to create FAT file systems on {@link BlockDevice}s which follow the + * "super floppy" standard. This means that the device will be formatted so + * that it does not contain a partition table. Instead, the entire device holds + * a single FAT file system. + * </p><p> + * This class follows the "builder" pattern, which means it's methods always + * returns the {@code SuperFloppyFormatter} instance they're called on. This + * allows to chain the method calls like this: + * <pre> + * BlockDevice dev = new RamDisk(16700000); + * FatFileSystem fs = SuperFloppyFormatter.get(dev). + * setFatType(FatType.FAT12).format(); + * </pre> + * + * </p> + * + * @author Matthias Treydte <matthias.treydte at meetwise.com> + */ +public final class SuperFloppyFormatter { + + /** + * The media descriptor used (hard disk). + */ + public final static int MEDIUM_DESCRIPTOR_HD = 0xf8; + + /** + * The default number of FATs. + */ + public final static int DEFAULT_FAT_COUNT = 2; + + /** + * The default number of sectors per track. + */ + public final static int DEFAULT_SECTORS_PER_TRACK = 32; + + /** + * The default number of heads. + * + * @since 0.6 + */ + public final static int DEFAULT_HEADS = 64; + + /** + * The default number of heads. + * + * @deprecated the name of this constant was mistyped + * @see #DEFAULT_HEADS + */ + @Deprecated + public final static int DEFULT_HEADS = DEFAULT_HEADS; + + /** + * The default OEM name for file systems created by this class. + */ + public final static String DEFAULT_OEM_NAME = "fat32lib"; //NOI18N + + private static final int MAX_DIRECTORY = 512; + + private final BlockDevice device; + + private String label; + private String oemName; + private FatType fatType; + private int sectorsPerCluster; + private int reservedSectors; + private int fatCount; + + /** + * Creates a new {@code SuperFloppyFormatter} for the specified + * {@code BlockDevice}. + * + * @param device + * @throws IOException on error accessing the specified {@code device} + */ + private SuperFloppyFormatter(BlockDevice device) throws IOException { + this.device = device; + this.oemName = DEFAULT_OEM_NAME; + this.fatCount = DEFAULT_FAT_COUNT; + setFatType(fatTypeFromDevice()); + } + + /** + * Retruns a {@code SuperFloppyFormatter} instance suitable for formatting + * the specified device. + * + * @param dev the device that should be formatted + * @return the formatter for the device + * @throws IOException on error creating the formatter + */ + public static SuperFloppyFormatter get(BlockDevice dev) throws IOException { + return new SuperFloppyFormatter(dev); + } + + /** + * Returns the OEM name that will be written to the {@link BootSector}. + * + * @return the OEM name of the new file system + */ + public String getOemName() { + return oemName; + } + + /** + * Sets the OEM name of the boot sector. + * + * TODO: throw an exception early if name is invalid (too long, ...) + * + * @param oemName the new OEM name + * @return this {@code SuperFloppyFormatter} + * @see BootSector#setOemName(java.lang.String) + */ + public SuperFloppyFormatter setOemName(String oemName) { + this.oemName = oemName; + return this; + } + + /** + * Sets the volume label of the file system to create. + * + * TODO: throw an exception early if label is invalid (too long, ...) + * + * @param label the new file system label, may be {@code null} + * @return this {@code SuperFloppyFormatter} + * @see FatFileSystem#setVolumeLabel(java.lang.String) + */ + public SuperFloppyFormatter setVolumeLabel(String label) { + this.label = label; + return this; + } + + /** + * Returns the volume label that will be given to the new file system. + * + * @return the file system label, may be {@code null} + * @see FatFileSystem#getVolumeLabel() + */ + public String getVolumeLabel() { + return label; + } + + private void initBootSector(BootSector bs) + throws IOException { + + bs.init(); + bs.setFileSystemTypeLabel(fatType.getLabel()); + bs.setNrReservedSectors(reservedSectors); + bs.setNrFats(fatCount); + bs.setSectorsPerCluster(sectorsPerCluster); + bs.setMediumDescriptor(MEDIUM_DESCRIPTOR_HD); + bs.setSectorsPerTrack(DEFAULT_SECTORS_PER_TRACK); + bs.setNrHeads(DEFAULT_HEADS); + bs.setOemName(oemName); + } + + /** + * Initializes the boot sector and file system for the device. The file + * system created by this method will always be in read-write mode. + * + * @return the file system that was created + * @throws IOException on write error + */ + public FatFileSystem format() throws IOException { + final int sectorSize = device.getSectorSize(); + final int totalSectors = (int)(device.getSize() / sectorSize); + + final FsInfoSector fsi; + final BootSector bs; + if (sectorsPerCluster == 0) throw new AssertionError(); + + if (fatType == FatType.FAT32) { + bs = new Fat32BootSector(device); + initBootSector(bs); + + final Fat32BootSector f32bs = (Fat32BootSector) bs; + + f32bs.setFsInfoSectorNr(1); + + f32bs.setSectorsPerFat(sectorsPerFat(0, totalSectors)); + final Random rnd = new Random(System.currentTimeMillis()); + f32bs.setFileSystemId(rnd.nextInt()); + + f32bs.setVolumeLabel(label); + + /* create FS info sector */ + fsi = FsInfoSector.create(f32bs); + } else { + bs = new Fat16BootSector(device); + initBootSector(bs); + + final Fat16BootSector f16bs = (Fat16BootSector) bs; + + final int rootDirEntries = rootDirectorySize( + device.getSectorSize(), totalSectors); + + f16bs.setRootDirEntryCount(rootDirEntries); + f16bs.setSectorsPerFat(sectorsPerFat(rootDirEntries, totalSectors)); + if (label != null) f16bs.setVolumeLabel(label); + fsi = null; + } + + +// bs.write(); + + if (fatType == FatType.FAT32) { + Fat32BootSector f32bs = (Fat32BootSector) bs; + /* possibly writes the boot sector copy */ + f32bs.writeCopy(device); + } + + final Fat fat = Fat.create(bs, 0); + + final AbstractDirectory rootDirStore; + if (fatType == FatType.FAT32) { + rootDirStore = ClusterChainDirectory.createRoot(fat); + fsi.setFreeClusterCount(fat.getFreeClusterCount()); + fsi.setLastAllocatedCluster(fat.getLastAllocatedCluster()); + fsi.write(); + } else { + rootDirStore = Fat16RootDirectory.create((Fat16BootSector) bs); + } + + final FatLfnDirectory rootDir = + new FatLfnDirectory(rootDirStore, fat, false); + + rootDir.flush(); + + for (int i = 0; i < bs.getNrFats(); i++) { + fat.writeCopy(FatUtils.getFatOffset(bs, i)); + } + + bs.write(); + + FatFileSystem fs = FatFileSystem.read(device, false); + + if (label != null) { + fs.setVolumeLabel(label); + } + + fs.flush(); + return fs; + } + + private int sectorsPerFat(int rootDirEntries, int totalSectors) + throws IOException { + + final int bps = device.getSectorSize(); + final int rootDirSectors = + ((rootDirEntries * 32) + (bps - 1)) / bps; + final long tmp1 = + totalSectors - (this.reservedSectors + rootDirSectors); + int tmp2 = (256 * this.sectorsPerCluster) + this.fatCount; + + if (fatType == FatType.FAT32) + tmp2 /= 2; + + final int result = (int) ((tmp1 + (tmp2 - 1)) / tmp2); + + return result; + } + + /** + * Determines a usable FAT type from the {@link #device} by looking at the + * {@link BlockDevice#getSize() device size} only. + * + * @return the suggested FAT type + * @throws IOException on error determining the device's size + */ + private FatType fatTypeFromDevice() throws IOException { + return fatTypeFromSize(device.getSize()); + } + + /** + * Determines a usable FAT type from the {@link #device} by looking at the + * {@link BlockDevice#getSize() device size} only. + * + * @return the suggested FAT type + * @throws IOException on error determining the device's size + */ + public static FatType fatTypeFromSize(long sizeInBytes) { + final long sizeInMb = sizeInBytes / (1024 * 1024); + if (sizeInMb < 4) return FatType.FAT12; + else if (sizeInMb < 512) return FatType.FAT16; + else return FatType.FAT32; + } + + public static int clusterSizeFromSize(long sizeInBytes, int sectorSize){ + switch(fatTypeFromSize(sizeInBytes)) { + case FAT12: + return sectorsPerCluster12(sizeInBytes, sectorSize); + case FAT16: + return sectorsPerCluster16FromSize(sizeInBytes, sectorSize); + case FAT32: + return sectorsPerCluster32FromSize(sizeInBytes, sectorSize); + + default: + throw new AssertionError(); + } + } + + /** + * Returns the exact type of FAT the will be created by this formatter. + * + * @return the FAT type + */ + public FatType getFatType() { + return this.fatType; + } + + /** + * Sets the type of FAT that will be created by this + * {@code SuperFloppyFormatter}. + * + * @param fatType the desired {@code FatType} + * @return this {@code SuperFloppyFormatter} + * @throws IOException on error setting the {@code fatType} + * @throws IllegalArgumentException if {@code fatType} does not support the + * size of the device + */ + public SuperFloppyFormatter setFatType(FatType fatType) + throws IOException, IllegalArgumentException { + + if (fatType == null) throw new NullPointerException(); + + switch (fatType) { + case FAT12: case FAT16: + this.reservedSectors = 1; + break; + + case FAT32: + this.reservedSectors = 32; + } + + this.sectorsPerCluster = defaultSectorsPerCluster(fatType); + this.fatType = fatType; + + return this; + } + + private static int rootDirectorySize(int bps, int nbTotalSectors) { + final int totalSize = bps * nbTotalSectors; + if (totalSize >= MAX_DIRECTORY * 5 * 32) { + return MAX_DIRECTORY; + } else { + return totalSize / (5 * 32); + } + } + + static private int MAX_FAT32_CLUSTERS = 0x0FFFFFF5; + + static private int sectorsPerCluster32FromSize(long size, int sectorSize) { + final long sectors = size / sectorSize; + + if (sectors <= 66600) throw new IllegalArgumentException( + "disk too small for FAT32"); + + return + sectors > 67108864 ? 64 : + sectors > 33554432 ? 32 : + sectors > 16777216 ? 16 : + sectors > 532480 ? 8 : 1; + } + + private int sectorsPerCluster32() throws IOException { + if (this.reservedSectors != 32) throw new IllegalStateException( + "number of reserved sectors must be 32"); + + if (this.fatCount != 2) throw new IllegalStateException( + "number of FATs must be 2"); + + final long sectors = device.getSize() / device.getSectorSize(); + + if (sectors <= 66600) throw new IllegalArgumentException( + "disk too small for FAT32"); + + return sectorsPerCluster32FromSize(device.getSize(), device.getSectorSize()); + } + + static private int MAX_FAT16_CLUSTERS = 65524; + + static private int sectorsPerCluster16FromSize(long size, int sectorSize) { + final long sectors = size / sectorSize; + + if (sectors <= 8400) throw new IllegalArgumentException( + "disk too small for FAT16"); + + if (sectors > 4194304) throw new IllegalArgumentException( + "disk too large for FAT16"); + + return + sectors > 2097152 ? 64 : + sectors > 1048576 ? 32 : + sectors > 524288 ? 16 : + sectors > 262144 ? 8 : + sectors > 32680 ? 4 : 2; + } + + private int sectorsPerCluster16() throws IOException { + if (this.reservedSectors != 1) throw new IllegalStateException( + "number of reserved sectors must be 1"); + + if (this.fatCount != 2) throw new IllegalStateException( + "number of FATs must be 2"); + + long size = device.getSize(); + int sectorSize = device.getSectorSize(); + return sectorsPerCluster16FromSize(size, sectorSize); + } + + private int defaultSectorsPerCluster(FatType fatType) throws IOException { + long size = device.getSize(); + int sectorSize = device.getSectorSize(); + + switch (fatType) { + case FAT12: + return sectorsPerCluster12(size, sectorSize); + + case FAT16: + return sectorsPerCluster16(); + + case FAT32: + return sectorsPerCluster32(); + + default: + throw new AssertionError(); + } + } + + static private int sectorsPerCluster12(long size, int sectorSize) { + int result = 1; + + final long sectors = size / sectorSize; + + while (sectors / result > Fat16BootSector.MAX_FAT12_CLUSTERS) { + result *= 2; + if (result * size > 4096) throw new + IllegalArgumentException("disk too large for FAT12"); + } + + return result; + } + +} |