• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &lt;matthias.treydte at meetwise.com&gt;
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