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.AbstractFsObject; 23 import java.nio.ByteBuffer; 24 25 /** 26 * 27 * 28 * @author Ewout Prangsma <epr at jnode.org> 29 * @author Matthias Treydte <waldheinz at gmail.com> 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 FatDirectoryEntry(byte[] data, boolean readOnly)69 FatDirectoryEntry(byte[] data, boolean readOnly) { 70 super(readOnly); 71 72 this.data = data; 73 } 74 FatDirectoryEntry()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 */ read(ByteBuffer buff, boolean readOnly)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 writeNullEntry(ByteBuffer buff)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 */ isVolumeLabel()120 public boolean isVolumeLabel() { 121 if (isLfnEntry()) return false; 122 else return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == F_VOLUME_ID); 123 } 124 setFlag(int mask, boolean set)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 isSystemFlag()139 public boolean isSystemFlag() { 140 return ((getFlags() & F_SYSTEM) != 0); 141 } 142 setSystemFlag(boolean isSystem)143 public void setSystemFlag(boolean isSystem) { 144 setFlag(F_SYSTEM, isSystem); 145 } 146 isArchiveFlag()147 public boolean isArchiveFlag() { 148 return ((getFlags() & F_ARCHIVE) != 0); 149 } 150 setArchiveFlag(boolean isArchive)151 public void setArchiveFlag(boolean isArchive) { 152 setFlag(F_ARCHIVE, isArchive); 153 } 154 isHiddenFlag()155 public boolean isHiddenFlag() { 156 return ((getFlags() & F_HIDDEN) != 0); 157 } 158 setHiddenFlag(boolean isHidden)159 public void setHiddenFlag(boolean isHidden) { 160 setFlag(F_HIDDEN, isHidden); 161 } 162 isVolumeIdFlag()163 public boolean isVolumeIdFlag() { 164 return ((getFlags() & F_VOLUME_ID) != 0); 165 } 166 isLfnEntry()167 public boolean isLfnEntry() { 168 return isReadonlyFlag() && isSystemFlag() && 169 isHiddenFlag() && isVolumeIdFlag(); 170 } 171 isDirty()172 public boolean isDirty() { 173 return dirty; 174 } 175 getFlags()176 private int getFlags() { 177 return LittleEndian.getUInt8(data, OFFSET_ATTRIBUTES); 178 } 179 setFlags(int flags)180 private void setFlags(int flags) { 181 LittleEndian.setInt8(data, OFFSET_ATTRIBUTES, flags); 182 } 183 isDirectory()184 public boolean isDirectory() { 185 return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == F_DIRECTORY); 186 } 187 create(boolean directory)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 createVolumeLabel(String volumeLabel)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 getVolumeLabel()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 getCreated()239 public long getCreated() { 240 return DosUtils.decodeDateTime( 241 LittleEndian.getUInt16(data, 0x10), 242 LittleEndian.getUInt16(data, 0x0e)); 243 } 244 setCreated(long created)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 getLastModified()254 public long getLastModified() { 255 return DosUtils.decodeDateTime( 256 LittleEndian.getUInt16(data, 0x18), 257 LittleEndian.getUInt16(data, 0x16)); 258 } 259 setLastModified(long lastModified)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 getLastAccessed()269 public long getLastAccessed() { 270 return DosUtils.decodeDateTime( 271 LittleEndian.getUInt16(data, 0x12), 272 0); /* time is not recorded */ 273 } 274 setLastAccessed(long lastAccessed)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 */ isDeleted()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 */ getLength()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 */ setLength(long length)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 */ getShortName()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 */ isFile()331 public boolean isFile() { 332 return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == 0); 333 } 334 setShortName(ShortName sn)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 */ getStartCluster()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 */ setStartCluster(long startCluster)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 toString()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 */ write(ByteBuffer buff)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 */ isReadonlyFlag()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 */ setReadonlyFlag(boolean isReadonly)401 public void setReadonlyFlag(boolean isReadonly) { 402 setFlag(F_READONLY, isReadonly); 403 } 404 getLfnPart()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