1 /*
2  * Copyright (C) 2003-2009 JNode.org
3  *               2009,2010 Matthias Treydte <mt@waldheinz.de>
4  *
5  * This library is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation; either version 2.1 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13  * License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library; If not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package de.waldheinz.fs.fat;
21 
22 import de.waldheinz.fs.AbstractFileSystem;
23 import de.waldheinz.fs.BlockDevice;
24 import java.io.IOException;
25 import de.waldheinz.fs.ReadOnlyException;
26 
27 /**
28  * <p>
29  * Implements the {@code FileSystem} interface for the FAT family of file
30  * systems. This class always uses the "long file name" specification when
31  * writing directory entries.
32  * </p><p>
33  * For creating (aka "formatting") FAT file systems please refer to the
34  * {@link SuperFloppyFormatter} class.
35  * </p>
36  *
37  * @author Ewout Prangsma &lt;epr at jnode.org&gt;
38  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
39  */
40 public final class FatFileSystem extends AbstractFileSystem {
41 
42     private final Fat fat;
43     private final FsInfoSector fsiSector;
44     private final BootSector bs;
45     private final FatLfnDirectory rootDir;
46     private final AbstractDirectory rootDirStore;
47     private final FatType fatType;
48     private final long filesOffset;
49 
FatFileSystem(BlockDevice api, boolean readOnly)50     FatFileSystem(BlockDevice api, boolean readOnly) throws IOException {
51 
52         this(api, readOnly, false);
53     }
54 
55     /**
56      * Constructor for FatFileSystem in specified readOnly mode
57      *
58      * @param device the {@code BlockDevice} holding the file system
59      * @param readOnly if this FS should be read-lonly
60      * @param ignoreFatDifferences
61      * @throws IOException on read error
62      */
FatFileSystem(BlockDevice device, boolean readOnly, boolean ignoreFatDifferences)63     private FatFileSystem(BlockDevice device, boolean readOnly,
64             boolean ignoreFatDifferences)
65             throws IOException {
66 
67         super(readOnly);
68 
69         this.bs = BootSector.read(device);
70 
71         if (bs.getNrFats() <= 0) throw new IOException(
72                 "boot sector says there are no FATs");
73 
74         this.filesOffset = FatUtils.getFilesOffset(bs);
75         this.fatType = bs.getFatType();
76         this.fat = Fat.read(bs, 0);
77 
78         if (!ignoreFatDifferences) {
79             for (int i=1; i < bs.getNrFats(); i++) {
80                 final Fat tmpFat = Fat.read(bs, i);
81                 if (!fat.equals(tmpFat)) {
82                     throw new IOException("FAT " + i + " differs from FAT 0");
83                 }
84             }
85         }
86 
87         if (fatType == FatType.FAT32) {
88             final Fat32BootSector f32bs = (Fat32BootSector) bs;
89             ClusterChain rootDirFile = new ClusterChain(fat,
90                     f32bs.getRootDirFirstCluster(), isReadOnly());
91             this.rootDirStore = ClusterChainDirectory.readRoot(rootDirFile);
92             this.fsiSector = FsInfoSector.read(f32bs);
93 
94             if (fsiSector.getFreeClusterCount() != fat.getFreeClusterCount()) {
95                 throw new IOException("free cluster count mismatch - fat: " +
96                         fat.getFreeClusterCount() + " - fsinfo: " +
97                         fsiSector.getFreeClusterCount());
98             }
99         } else {
100             this.rootDirStore =
101                     Fat16RootDirectory.read((Fat16BootSector) bs,readOnly);
102             this.fsiSector = null;
103         }
104 
105         this.rootDir = new FatLfnDirectory(rootDirStore, fat, isReadOnly());
106 
107     }
108 
109     /**
110      * Reads the file system structure from the specified {@code BlockDevice}
111      * and returns a fresh {@code FatFileSystem} instance to read or modify
112      * it.
113      *
114      * @param device the {@code BlockDevice} holding the file system
115      * @param readOnly if the {@code FatFileSystem} should be in read-only mode
116      * @return the {@code FatFileSystem} instance for the device
117      * @throws IOException on read error or if the file system structure could
118      *      not be parsed
119      */
read(BlockDevice device, boolean readOnly)120     public static FatFileSystem read(BlockDevice device, boolean readOnly)
121             throws IOException {
122 
123         return new FatFileSystem(device, readOnly);
124     }
125 
getFilesOffset()126     long getFilesOffset() {
127         checkClosed();
128 
129         return filesOffset;
130     }
131 
132     /**
133      * Returns the size of the FAT entries of this {@code FatFileSystem}.
134      *
135      * @return the exact type of the FAT used by this file system
136      */
getFatType()137     public FatType getFatType() {
138         checkClosed();
139 
140         return this.fatType;
141     }
142 
143     /**
144      * Returns the volume label of this file system.
145      *
146      * @return the volume label
147      */
getVolumeLabel()148     public String getVolumeLabel() {
149         checkClosed();
150 
151         final String fromDir = rootDirStore.getLabel();
152 
153         if (fromDir == null && fatType != FatType.FAT32) {
154             return ((Fat16BootSector)bs).getVolumeLabel();
155         } else {
156             return fromDir;
157         }
158     }
159 
160     /**
161      * Sets the volume label for this file system.
162      *
163      * @param label the new volume label, may be {@code null}
164      * @throws ReadOnlyException if the file system is read-only
165      * @throws IOException on write error
166      */
setVolumeLabel(String label)167     public void setVolumeLabel(String label)
168             throws ReadOnlyException, IOException {
169 
170         checkClosed();
171         checkReadOnly();
172 
173         rootDirStore.setLabel(label);
174 
175         if (fatType != FatType.FAT32) {
176             ((Fat16BootSector)bs).setVolumeLabel(label);
177         }
178     }
179 
getRootDirStore()180     AbstractDirectory getRootDirStore() {
181         checkClosed();
182 
183         return rootDirStore;
184     }
185 
186     /**
187      * Flush all changed structures to the device.
188      *
189      * @throws IOException on write error
190      */
191     @Override
flush()192     public void flush() throws IOException {
193         checkClosed();
194 
195         if (bs.isDirty()) {
196             bs.write();
197         }
198 
199         for (int i = 0; i < bs.getNrFats(); i++) {
200             fat.writeCopy(FatUtils.getFatOffset(bs, i));
201         }
202 
203         rootDir.flush();
204 
205         if (fsiSector != null) {
206             fsiSector.setFreeClusterCount(fat.getFreeClusterCount());
207             fsiSector.setLastAllocatedCluster(fat.getLastAllocatedCluster());
208             fsiSector.write();
209         }
210     }
211 
212     @Override
getRoot()213     public FatLfnDirectory getRoot() {
214         checkClosed();
215 
216         return rootDir;
217     }
218 
219     /**
220      * Returns the fat.
221      *
222      * @return Fat
223      */
getFat()224     public Fat getFat() {
225         return fat;
226     }
227 
228     /**
229      * Returns the bootsector.
230      *
231      * @return BootSector
232      */
getBootSector()233     public BootSector getBootSector() {
234         checkClosed();
235 
236         return bs;
237     }
238 
239     /**
240      * <p>
241      * {@inheritDoc}
242      * </p><p>
243      * This method shows the free space in terms of available clusters.
244      * </p>
245      *
246      * @return always -1
247      */
248     @Override
getFreeSpace()249     public long getFreeSpace() {
250     	return this.fat.getFreeClusterCount() * this.bs.getBytesPerCluster();
251     }
252 
253     /**
254      * <p>
255      * {@inheritDoc}
256      * </p><p>
257      * This method is currently not implemented for {@code FatFileSystem} and
258      * always returns -1.
259      * </p>
260      *
261      * @return always -1
262      */
263     @Override
getTotalSpace()264     public long getTotalSpace() {
265     	return this.bs.getDataClusterCount() * this.bs.getBytesPerCluster();
266     }
267 
268     /**
269      * <p>
270      * {@inheritDoc}
271      * </p><p>
272      * This method is currently not implemented for {@code FatFileSystem} and
273      * always returns -1.
274      * </p>
275      *
276      * @return always -1
277      */
278     @Override
getUsableSpace()279     public long getUsableSpace() {
280         // TODO implement me
281         return -1;
282     }
283 }
284