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 java.nio.ByteBuffer;
     24 
     25 /**
     26  *
     27  *
     28  * @author Ewout Prangsma &lt;epr at jnode.org&gt;
     29  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
     30  */
     31 final class FatDirectoryEntry extends AbstractFsObject {
     32 
     33     /**
     34      * The size in bytes of an FAT directory entry.
     35      */
     36     public final static int SIZE = 32;
     37 
     38     /**
     39      * The offset to the attributes byte.
     40      */
     41     private static final int OFFSET_ATTRIBUTES = 0x0b;
     42 
     43     /**
     44      * The offset to the file size dword.
     45      */
     46     private static final int OFFSET_FILE_SIZE = 0x1c;
     47 
     48     private static final int F_READONLY = 0x01;
     49     private static final int F_HIDDEN = 0x02;
     50     private static final int F_SYSTEM = 0x04;
     51     private static final int F_VOLUME_ID = 0x08;
     52     private static final int F_DIRECTORY = 0x10;
     53     private static final int F_ARCHIVE = 0x20;
     54 
     55     private static final int MAX_CLUSTER = 0xFFFF;
     56 
     57     /**
     58      * The magic byte denoting that this entry was deleted and is free
     59      * for reuse.
     60      *
     61      * @see #isDeleted()
     62      */
     63     public static final int ENTRY_DELETED_MAGIC = 0xe5;
     64 
     65     private final byte[] data;
     66     private boolean dirty;
     67     boolean hasShortNameOnly;
     68 
     69     FatDirectoryEntry(byte[] data, boolean readOnly) {
     70         super(readOnly);
     71 
     72         this.data = data;
     73     }
     74 
     75     private FatDirectoryEntry() {
     76         this(new byte[SIZE], false);
     77 
     78     }
     79 
     80     /**
     81      * Reads a {@code FatDirectoryEntry} from the specified {@code ByteBuffer}.
     82      * The buffer must have at least {@link #SIZE} bytes remaining. The entry
     83      * is read from the buffer's current position, and if this method returns
     84      * non-null the position will have advanced by {@link #SIZE} bytes,
     85      * otherwise the position will remain unchanged.
     86      *
     87      * @param buff the buffer to read the entry from
     88      * @param readOnly if the resulting {@code FatDirecoryEntry} should be
     89      *      read-only
     90      * @return the directory entry that was read from the buffer or {@code null}
     91      *      if there was no entry to read from the specified position (first
     92      *      byte was 0)
     93      */
     94     public static FatDirectoryEntry read(ByteBuffer buff, boolean readOnly) {
     95         assert (buff.remaining() >= SIZE);
     96 
     97         /* peek into the buffer to see if we're done with reading */
     98 
     99         if (buff.get(buff.position()) == 0) return null;
    100 
    101         /* read the directory entry */
    102 
    103         final byte[] data = new byte[SIZE];
    104         buff.get(data);
    105         return new FatDirectoryEntry(data, readOnly);
    106     }
    107 
    108     public static void writeNullEntry(ByteBuffer buff) {
    109         for (int i=0; i < SIZE; i++) {
    110             buff.put((byte) 0);
    111         }
    112     }
    113 
    114     /**
    115      * Decides if this entry is a "volume label" entry according to the FAT
    116      * specification.
    117      *
    118      * @return if this is a volume label entry
    119      */
    120     public boolean isVolumeLabel() {
    121         if (isLfnEntry()) return false;
    122         else return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == F_VOLUME_ID);
    123     }
    124 
    125     private void setFlag(int mask, boolean set) {
    126         final int oldFlags = getFlags();
    127 
    128         if (((oldFlags & mask) != 0) == set) return;
    129 
    130         if (set) {
    131             setFlags(oldFlags | mask);
    132         } else {
    133             setFlags(oldFlags & ~mask);
    134         }
    135 
    136         this.dirty = true;
    137     }
    138 
    139     public boolean isSystemFlag() {
    140         return ((getFlags() & F_SYSTEM) != 0);
    141     }
    142 
    143     public void setSystemFlag(boolean isSystem) {
    144         setFlag(F_SYSTEM, isSystem);
    145     }
    146 
    147     public boolean isArchiveFlag() {
    148         return ((getFlags() & F_ARCHIVE) != 0);
    149     }
    150 
    151     public void setArchiveFlag(boolean isArchive) {
    152         setFlag(F_ARCHIVE, isArchive);
    153     }
    154 
    155     public boolean isHiddenFlag() {
    156         return ((getFlags() & F_HIDDEN) != 0);
    157     }
    158 
    159     public void setHiddenFlag(boolean isHidden) {
    160         setFlag(F_HIDDEN, isHidden);
    161     }
    162 
    163     public boolean isVolumeIdFlag() {
    164         return ((getFlags() & F_VOLUME_ID) != 0);
    165     }
    166 
    167     public boolean isLfnEntry() {
    168         return isReadonlyFlag() && isSystemFlag() &&
    169                 isHiddenFlag() && isVolumeIdFlag();
    170     }
    171 
    172     public boolean isDirty() {
    173         return dirty;
    174     }
    175 
    176     private int getFlags() {
    177         return LittleEndian.getUInt8(data, OFFSET_ATTRIBUTES);
    178     }
    179 
    180     private void setFlags(int flags) {
    181         LittleEndian.setInt8(data, OFFSET_ATTRIBUTES, flags);
    182     }
    183 
    184     public boolean isDirectory() {
    185         return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == F_DIRECTORY);
    186     }
    187 
    188     public static FatDirectoryEntry create(boolean directory) {
    189         final FatDirectoryEntry result = new FatDirectoryEntry();
    190 
    191         if (directory) {
    192             result.setFlags(F_DIRECTORY);
    193         }
    194 
    195         /* initialize date and time fields */
    196 
    197         final long now = System.currentTimeMillis();
    198         result.setCreated(now);
    199         result.setLastAccessed(now);
    200         result.setLastModified(now);
    201 
    202         return result;
    203     }
    204 
    205     public static FatDirectoryEntry createVolumeLabel(String volumeLabel) {
    206         assert(volumeLabel != null);
    207 
    208         final byte[] data = new byte[SIZE];
    209 
    210         System.arraycopy(
    211                     volumeLabel.getBytes(), 0,
    212                     data, 0,
    213                     volumeLabel.length());
    214 
    215         final FatDirectoryEntry result = new FatDirectoryEntry(data, false);
    216         result.setFlags(FatDirectoryEntry.F_VOLUME_ID);
    217         return result;
    218     }
    219 
    220     public String getVolumeLabel() {
    221         if (!isVolumeLabel())
    222             throw new UnsupportedOperationException("not a volume label");
    223 
    224         final StringBuilder sb = new StringBuilder();
    225 
    226         for (int i=0; i < AbstractDirectory.MAX_LABEL_LENGTH; i++) {
    227             final byte b = this.data[i];
    228 
    229             if (b != 0) {
    230                 sb.append((char) b);
    231             } else {
    232                 break;
    233             }
    234         }
    235 
    236         return sb.toString();
    237     }
    238 
    239     public long getCreated() {
    240         return DosUtils.decodeDateTime(
    241                 LittleEndian.getUInt16(data, 0x10),
    242                 LittleEndian.getUInt16(data, 0x0e));
    243     }
    244 
    245     public void setCreated(long created) {
    246         LittleEndian.setInt16(data, 0x0e,
    247                 DosUtils.encodeTime(created));
    248         LittleEndian.setInt16(data, 0x10,
    249                 DosUtils.encodeDate(created));
    250 
    251         this.dirty = true;
    252     }
    253 
    254     public long getLastModified() {
    255         return DosUtils.decodeDateTime(
    256                 LittleEndian.getUInt16(data, 0x18),
    257                 LittleEndian.getUInt16(data, 0x16));
    258     }
    259 
    260     public void setLastModified(long lastModified) {
    261         LittleEndian.setInt16(data, 0x16,
    262                 DosUtils.encodeTime(lastModified));
    263         LittleEndian.setInt16(data, 0x18,
    264                 DosUtils.encodeDate(lastModified));
    265 
    266         this.dirty = true;
    267     }
    268 
    269     public long getLastAccessed() {
    270         return DosUtils.decodeDateTime(
    271                 LittleEndian.getUInt16(data, 0x12),
    272                 0); /* time is not recorded */
    273     }
    274 
    275     public void setLastAccessed(long lastAccessed) {
    276         LittleEndian.setInt16(data, 0x12,
    277                 DosUtils.encodeDate(lastAccessed));
    278 
    279         this.dirty = true;
    280     }
    281 
    282     /**
    283      * Returns if this entry has been marked as deleted. A deleted entry has
    284      * its first byte set to the magic {@link #ENTRY_DELETED_MAGIC} value.
    285      *
    286      * @return if this entry is marked as deleted
    287      */
    288     public boolean isDeleted() {
    289         return  (LittleEndian.getUInt8(data, 0) == ENTRY_DELETED_MAGIC);
    290     }
    291 
    292     /**
    293      * Returns the size of this entry as stored at {@link #OFFSET_FILE_SIZE}.
    294      *
    295      * @return the size of the file represented by this entry
    296      */
    297     public long getLength() {
    298         return LittleEndian.getUInt32(data, OFFSET_FILE_SIZE);
    299     }
    300 
    301     /**
    302      * Sets the size of this entry stored at {@link #OFFSET_FILE_SIZE}.
    303      *
    304      * @param length the new size of the file represented by this entry
    305      * @throws IllegalArgumentException if {@code length} is out of range
    306      */
    307     public void setLength(long length) throws IllegalArgumentException {
    308         LittleEndian.setInt32(data, OFFSET_FILE_SIZE, length);
    309     }
    310 
    311     /**
    312      * Returns the {@code ShortName} that is stored in this directory entry or
    313      * {@code null} if this entry has not been initialized.
    314      *
    315      * @return the {@code ShortName} stored in this entry or {@code null}
    316      */
    317     public ShortName getShortName() {
    318         if (this.data[0] == 0) {
    319             return null;
    320         } else {
    321             return ShortName.parse(this.data);
    322         }
    323     }
    324 
    325     /**
    326      * Does this entry refer to a file?
    327      *
    328      * @return
    329      * @see org.jnode.fs.FSDirectoryEntry#isFile()
    330      */
    331     public boolean isFile() {
    332         return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == 0);
    333     }
    334 
    335     public void setShortName(ShortName sn) {
    336         if (sn.equals(this.getShortName())) return;
    337 
    338         sn.write(this.data);
    339         this.hasShortNameOnly = sn.hasShortNameOnly();
    340         this.dirty = true;
    341     }
    342 
    343     /**
    344      * Returns the startCluster.
    345      *
    346      * @return int
    347      */
    348     public long getStartCluster() {
    349     	int lowBytes = LittleEndian.getUInt16(data, 0x1a);
    350         long highBytes = LittleEndian.getUInt16(data, 0x14);
    351         return ( highBytes << 16 | lowBytes );
    352     }
    353 
    354     /**
    355      * Sets the startCluster.
    356      *
    357      * @param startCluster The startCluster to set
    358      */
    359     void setStartCluster(long startCluster) {
    360         if ( startCluster > Integer.MAX_VALUE ) throw new AssertionError();
    361 
    362         LittleEndian.setInt16(data, 0x1a, (int) startCluster);
    363         LittleEndian.setInt16(data, 0x14, (int)(startCluster >>> 16));
    364     }
    365 
    366     @Override
    367     public String toString() {
    368         return getClass().getSimpleName() +
    369                 " [name=" + getShortName() + "]"; //NOI18N
    370     }
    371 
    372     /**
    373      * Writes this directory entry into the specified buffer.
    374      *
    375      * @param buff the buffer to write this entry to
    376      */
    377     void write(ByteBuffer buff) {
    378         buff.put(data);
    379         this.dirty = false;
    380     }
    381 
    382     /**
    383      * Returns if the read-only flag is set for this entry. Do not confuse
    384      * this with {@link #isReadOnly()}.
    385      *
    386      * @return if the read only file system flag is set on this entry
    387      * @see #F_READONLY
    388      * @see #setReadonlyFlag(boolean)
    389      */
    390     public boolean isReadonlyFlag() {
    391         return ((getFlags() & F_READONLY) != 0);
    392     }
    393 
    394     /**
    395      * Updates the read-only file system flag for this entry.
    396      *
    397      * @param isReadonly the new value for the read-only flag
    398      * @see #F_READONLY
    399      * @see #isReadonlyFlag()
    400      */
    401     public void setReadonlyFlag(boolean isReadonly) {
    402         setFlag(F_READONLY, isReadonly);
    403     }
    404 
    405     String getLfnPart() {
    406         final char[] unicodechar = new char[13];
    407 
    408         unicodechar[0] = (char) LittleEndian.getUInt16(data, 1);
    409         unicodechar[1] = (char) LittleEndian.getUInt16(data, 3);
    410         unicodechar[2] = (char) LittleEndian.getUInt16(data, 5);
    411         unicodechar[3] = (char) LittleEndian.getUInt16(data, 7);
    412         unicodechar[4] = (char) LittleEndian.getUInt16(data, 9);
    413         unicodechar[5] = (char) LittleEndian.getUInt16(data, 14);
    414         unicodechar[6] = (char) LittleEndian.getUInt16(data, 16);
    415         unicodechar[7] = (char) LittleEndian.getUInt16(data, 18);
    416         unicodechar[8] = (char) LittleEndian.getUInt16(data, 20);
    417         unicodechar[9] = (char) LittleEndian.getUInt16(data, 22);
    418         unicodechar[10] = (char) LittleEndian.getUInt16(data, 24);
    419         unicodechar[11] = (char) LittleEndian.getUInt16(data, 28);
    420         unicodechar[12] = (char) LittleEndian.getUInt16(data, 30);
    421 
    422         int end = 0;
    423 
    424         while ((end < 13) && (unicodechar[end] != '\0')) {
    425             end++;
    426         }
    427 
    428         return new String(unicodechar).substring(0, end);
    429     }
    430 
    431 }
    432