Home | History | Annotate | Download | only in fat
      1 /*
      2  * Copyright (C) 2003-2009 JNode.org
      3  *               2009,2010 Matthias Treydte <mt (at) 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.AbstractFsObject;
     23 import de.waldheinz.fs.FsDirectory;
     24 import de.waldheinz.fs.FsDirectoryEntry;
     25 import java.io.IOException;
     26 import java.util.ArrayList;
     27 import java.util.Arrays;
     28 import java.util.HashSet;
     29 import java.util.LinkedHashMap;
     30 import java.util.Iterator;
     31 import java.util.Map;
     32 import java.util.Set;
     33 
     34 /**
     35  * The {@link FsDirectory} implementation for FAT file systems. This
     36  * implementation aims to fully comply to the FAT specification, including
     37  * the quite complex naming system regarding the long file names (LFNs) and
     38  * their corresponding 8+3 short file names. This also means that an
     39  * {@code FatLfnDirectory} is case-preserving but <em>not</em> case-sensitive.
     40  *
     41  * @author gbin
     42  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
     43  * @since 0.6
     44  */
     45 public final class FatLfnDirectory
     46         extends AbstractFsObject
     47         implements FsDirectory {
     48 
     49     /**
     50      * This set is used to check if a file name is already in use in this
     51      * directory. The FAT specification says that file names must be unique
     52      * ignoring the case, so this set contains all names converted to
     53      * lower-case, and all checks must be performed using lower-case strings.
     54      */
     55     private final Set<String> usedNames;
     56     private final Fat fat;
     57     private final Map<ShortName, FatLfnDirectoryEntry> shortNameIndex;
     58     private final Map<String, FatLfnDirectoryEntry> longNameIndex;
     59     private final Map<FatDirectoryEntry, FatFile> entryToFile;
     60     private final Map<FatDirectoryEntry, FatLfnDirectory> entryToDirectory;
     61     private Dummy83BufferGenerator dbg;
     62 
     63     final AbstractDirectory dir;
     64 
     65     FatLfnDirectory(AbstractDirectory dir, Fat fat, boolean readOnly)
     66             throws IOException {
     67 
     68         super(readOnly);
     69 
     70         if ((dir == null) || (fat == null)) throw new NullPointerException();
     71 
     72         this.fat = fat;
     73         this.dir = dir;
     74 
     75         this.shortNameIndex =
     76                 new LinkedHashMap<ShortName, FatLfnDirectoryEntry>();
     77 
     78         this.longNameIndex =
     79                 new LinkedHashMap<String, FatLfnDirectoryEntry>();
     80 
     81         this.entryToFile =
     82                 new LinkedHashMap<FatDirectoryEntry, FatFile>();
     83 
     84         this.entryToDirectory =
     85                 new LinkedHashMap<FatDirectoryEntry, FatLfnDirectory>();
     86 
     87         this.usedNames = new HashSet<String>();
     88         this.dbg = new Dummy83BufferGenerator();
     89 
     90         parseLfn();
     91     }
     92 
     93     FatFile getFile(FatDirectoryEntry entry) throws IOException {
     94         FatFile file = entryToFile.get(entry);
     95 
     96         if (file == null) {
     97             file = FatFile.get(fat, entry);
     98             entryToFile.put(entry, file);
     99         }
    100 
    101         return file;
    102     }
    103 
    104     FatLfnDirectory getDirectory(FatDirectoryEntry entry) throws IOException {
    105         FatLfnDirectory result = entryToDirectory.get(entry);
    106 
    107         if (result == null) {
    108             final ClusterChainDirectory storage = read(entry, fat);
    109             result = new FatLfnDirectory(storage, fat, isReadOnly());
    110             entryToDirectory.put(entry, result);
    111         }
    112 
    113         return result;
    114     }
    115 
    116     /**
    117      * <p>
    118      * {@inheritDoc}
    119      * </p><p>
    120      * According to the FAT file system specification, leading and trailing
    121      * spaces in the {@code name} are ignored by this method.
    122      * </p>
    123      *
    124      * @param name {@inheritDoc}
    125      * @return {@inheritDoc}
    126      * @throws IOException {@inheritDoc}
    127      */
    128     @Override
    129     public FatLfnDirectoryEntry addFile(String name) throws IOException {
    130         checkWritable();
    131         checkUniqueName(name);
    132 
    133         name = name.trim();
    134         final ShortName sn = makeShortName(name, false);
    135 
    136         final FatLfnDirectoryEntry entry =
    137                 new FatLfnDirectoryEntry(name, sn, this, false);
    138 
    139         dir.addEntries(entry.compactForm());
    140 
    141         shortNameIndex.put(sn, entry);
    142         longNameIndex.put(name.toLowerCase(), entry);
    143 
    144         getFile(entry.realEntry);
    145 
    146         dir.setDirty();
    147         return entry;
    148     }
    149 
    150     boolean isFreeName(String name) {
    151         return true;
    152     }
    153 
    154     private void checkUniqueName(String name) throws IOException {
    155     }
    156 
    157     private void freeUniqueName(String name) {
    158     }
    159 
    160     private ShortName makeShortName(String name, boolean isDirectory) throws IOException {
    161         final ShortName result;
    162 
    163         try {
    164             result = dbg.generate83BufferNew(name);
    165         } catch (IllegalArgumentException ex) {
    166             throw new IOException(
    167                     "could not generate short name for \"" + name + "\"", ex);
    168         }
    169         return result;
    170     }
    171 
    172     /**
    173      * <p>
    174      * {@inheritDoc}
    175      * </p><p>
    176      * According to the FAT file system specification, leading and trailing
    177      * spaces in the {@code name} are ignored by this method.
    178      * </p>
    179      *
    180      * @param name {@inheritDoc}
    181      * @return {@inheritDoc}
    182      * @throws IOException {@inheritDoc}
    183      */
    184     @Override
    185     public FatLfnDirectoryEntry addDirectory(String name) throws IOException {
    186         checkWritable();
    187         checkUniqueName(name);
    188 
    189         name = name.trim();
    190         final ShortName sn = makeShortName(name, true);
    191         final FatDirectoryEntry real = dir.createSub(fat);
    192         real.setShortName(sn);
    193         final FatLfnDirectoryEntry e =
    194                 new FatLfnDirectoryEntry(this, real, name);
    195 
    196         try {
    197             dir.addEntries(e.compactForm());
    198         } catch (IOException ex) {
    199             final ClusterChain cc =
    200                     new ClusterChain(fat, real.getStartCluster(), false);
    201             cc.setChainLength(0);
    202             dir.removeEntry(real);
    203             throw ex;
    204         }
    205 
    206         shortNameIndex.put(sn, e);
    207         longNameIndex.put(name.toLowerCase(), e);
    208 
    209         getDirectory(real);
    210 
    211         flush();
    212         return e;
    213     }
    214 
    215     /**
    216      * <p>
    217      * {@inheritDoc}
    218      * </p><p>
    219      * According to the FAT file system specification, leading and trailing
    220      * spaces in the {@code name} are ignored by this method.
    221      * </p>
    222      *
    223      * @param name {@inheritDoc}
    224      * @return {@inheritDoc}
    225      */
    226     @Override
    227     public FatLfnDirectoryEntry getEntry(String name) {
    228         name = name.trim().toLowerCase();
    229 
    230         final FatLfnDirectoryEntry entry = longNameIndex.get(name);
    231 
    232         if (entry == null) {
    233             if (!ShortName.canConvert(name)) return null;
    234             return shortNameIndex.get(ShortName.get(name));
    235         } else {
    236             return entry;
    237         }
    238     }
    239 
    240     private void parseLfn() throws IOException {
    241         int i = 0;
    242         final int size = dir.getEntryCount();
    243 
    244         while (i < size) {
    245             // jump over empty entries
    246             while (i < size && dir.getEntry(i) == null) {
    247                 i++;
    248             }
    249 
    250             if (i >= size) {
    251                 break;
    252             }
    253 
    254             int offset = i; // beginning of the entry
    255             // check when we reach a real entry
    256             while (dir.getEntry(i).isLfnEntry()) {
    257                 i++;
    258                 if (i >= size) {
    259                     // This is a cutted entry, forgive it
    260                     break;
    261                 }
    262             }
    263 
    264             if (i >= size) {
    265                 // This is a cutted entry, forgive it
    266                 break;
    267             }
    268 
    269             final FatLfnDirectoryEntry current =
    270                     FatLfnDirectoryEntry.extract(this, offset, ++i - offset);
    271 
    272             if (!current.realEntry.isDeleted() && current.isValid()) {
    273                 checkUniqueName(current.getName());
    274 
    275                 shortNameIndex.put(current.realEntry.getShortName(), current);
    276                 longNameIndex.put(current.getName().toLowerCase(), current);
    277             }
    278         }
    279     }
    280 
    281     private void updateLFN() throws IOException {
    282         ArrayList<FatDirectoryEntry> dest =
    283                 new ArrayList<FatDirectoryEntry>();
    284 
    285         for (FatLfnDirectoryEntry currentEntry : shortNameIndex.values()) {
    286             FatDirectoryEntry[] encoded = currentEntry.compactForm();
    287             dest.addAll(Arrays.asList(encoded));
    288         }
    289 
    290         final int size = dest.size();
    291 
    292         dir.changeSize(size);
    293         dir.setEntries(dest);
    294     }
    295 
    296     @Override
    297     public void flush() throws IOException {
    298         checkWritable();
    299 
    300         for (FatFile f : entryToFile.values()) {
    301             f.flush();
    302         }
    303 
    304         for (FatLfnDirectory d : entryToDirectory.values()) {
    305             d.flush();
    306         }
    307 
    308         updateLFN();
    309         dir.flush();
    310     }
    311 
    312     @Override
    313     public Iterator<FsDirectoryEntry> iterator() {
    314         return new Iterator<FsDirectoryEntry>() {
    315 
    316             final Iterator<FatLfnDirectoryEntry> it =
    317                     shortNameIndex.values().iterator();
    318 
    319             @Override
    320             public boolean hasNext() {
    321                 return it.hasNext();
    322             }
    323 
    324             @Override
    325             public FsDirectoryEntry next() {
    326                 return it.next();
    327             }
    328 
    329             /**
    330              * @see java.util.Iterator#remove()
    331              */
    332             @Override
    333             public void remove() {
    334                 throw new UnsupportedOperationException();
    335             }
    336         };
    337     }
    338 
    339     /**
    340      * Remove the entry with the given name from this directory.
    341      *
    342      * @param name the name of the entry to remove
    343      * @throws IOException on error removing the entry
    344      * @throws IllegalArgumentException on an attempt to remove the dot entries
    345      */
    346     @Override
    347     public void remove(String name)
    348             throws IOException, IllegalArgumentException {
    349 
    350         checkWritable();
    351 
    352         final FatLfnDirectoryEntry entry = getEntry(name);
    353         if (entry == null) return;
    354 
    355         unlinkEntry(entry);
    356 
    357         final ClusterChain cc = new ClusterChain(
    358                 fat, entry.realEntry.getStartCluster(), false);
    359 
    360         cc.setChainLength(0);
    361 
    362         freeUniqueName(name);
    363         updateLFN();
    364     }
    365 
    366     /**
    367      * Unlinks the specified entry from this directory without actually
    368      * deleting it.
    369      *
    370      * @param e the entry to be unlinked
    371      * @see #linkEntry(de.waldheinz.fs.fat.FatLfnDirectoryEntry)
    372      */
    373     void unlinkEntry(FatLfnDirectoryEntry entry) {
    374         final ShortName sn = entry.realEntry.getShortName();
    375 
    376         if (sn.equals(ShortName.DOT) || sn.equals(ShortName.DOT_DOT)) throw
    377                 new IllegalArgumentException(
    378                     "the dot entries can not be removed");
    379 
    380         final String lowerName = entry.getName().toLowerCase();
    381 
    382         assert (this.longNameIndex.containsKey(lowerName));
    383         this.longNameIndex.remove(lowerName);
    384 
    385         assert (this.shortNameIndex.containsKey(sn));
    386         this.shortNameIndex.remove(sn);
    387 
    388         if (entry.isFile()) {
    389             this.entryToFile.remove(entry.realEntry);
    390         } else {
    391             this.entryToDirectory.remove(entry.realEntry);
    392         }
    393     }
    394 
    395     /**
    396      * Links the specified entry to this directory, updating the entrie's
    397      * short name.
    398      *
    399      * @param entry the entry to be linked (added) to this directory
    400      * @see #unlinkEntry(de.waldheinz.fs.fat.FatLfnDirectoryEntry)
    401      */
    402     void linkEntry(FatLfnDirectoryEntry entry) throws IOException {
    403         checkUniqueName(entry.getName());
    404         ShortName name;
    405         name = this.dbg.generate83BufferNew(entry.getName());
    406         entry.realEntry.setShortName(name);
    407 
    408         this.longNameIndex.put(entry.getName().toLowerCase(), entry);
    409         this.shortNameIndex.put(entry.realEntry.getShortName(), entry);
    410 
    411         updateLFN();
    412     }
    413 
    414     @Override
    415     public String toString() {
    416         return getClass().getSimpleName() +
    417                 " [size=" + shortNameIndex.size() + //NOI18N
    418                 ", dir=" + dir + "]"; //NOI18N
    419     }
    420 
    421     private static ClusterChainDirectory read(FatDirectoryEntry entry, Fat fat)
    422             throws IOException {
    423 
    424         if (!entry.isDirectory()) throw
    425                 new IllegalArgumentException(entry + " is no directory");
    426 
    427         final ClusterChain chain = new ClusterChain(
    428                 fat, entry.getStartCluster(),
    429                 entry.isReadonlyFlag());
    430 
    431         final ClusterChainDirectory result =
    432                 new ClusterChainDirectory(chain, false);
    433 
    434         result.read();
    435         return result;
    436     }
    437 
    438 }
    439