summaryrefslogtreecommitdiff
path: root/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java')
-rw-r--r--src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java468
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 &lt;matthias.treydte at meetwise.com&gt;
+ */
+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;
+ }
+
+}