blob: 5a64639c3f8e28380afcf87e0ca64493afe015b5 [file] [log] [blame]
/*
* 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;
}
}