| /* |
| * 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; |
| } |
| |
| } |