1 /* 2 * Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de. 3 * Copyright 2008, François Revol <revol@free.fr> 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "Directory.h" 9 10 #include <stdint.h> 11 #include <stdio.h> 12 #include <string.h> 13 #include <strings.h> 14 #include <unistd.h> 15 16 #include <new> 17 18 #include <StorageDefs.h> 19 20 #include "CachedBlock.h" 21 #include "File.h" 22 #include "Volume.h" 23 24 25 //#define TRACE(x) dprintf x 26 #define TRACE(x) do {} while (0) 27 28 29 using std::nothrow; 30 31 32 namespace FATFS { 33 34 35 struct dir_entry { 36 void *Buffer() const { return (void *)fName; }; 37 const char *BaseName() const { return fName; }; 38 const char *Extension() const { return fExt; }; 39 uint8 Flags() const { return fFlags; }; 40 uint32 Cluster(int32 fatBits) const; 41 void SetCluster(uint32 cluster, int32 fatBits); 42 uint32 Size() const { return B_LENDIAN_TO_HOST_INT32(fSize); }; 43 void SetSize(uint32 size); 44 bool IsFile() const; 45 bool IsDir() const; 46 47 char fName[8]; 48 char fExt[3]; 49 uint8 fFlags; 50 uint8 fReserved1; 51 uint8 fCreateTime10ms; 52 uint16 fCreateTime; 53 uint16 fCreateDate; 54 uint16 fAccessDate; 55 uint16 fClusterMSB; 56 uint16 fModifiedTime; 57 uint16 fModifiedDate; 58 uint16 fClusterLSB; 59 uint32 fSize; 60 } _PACKED; 61 62 63 uint32 64 dir_entry::Cluster(int32 fatBits) const 65 { 66 uint32 c = B_LENDIAN_TO_HOST_INT16(fClusterLSB); 67 if (fatBits == 32) 68 c += ((uint32)B_LENDIAN_TO_HOST_INT16(fClusterMSB) << 16); 69 return c; 70 } 71 72 73 void 74 dir_entry::SetCluster(uint32 cluster, int32 fatBits) 75 { 76 fClusterLSB = B_HOST_TO_LENDIAN_INT16((uint16)cluster); 77 if (fatBits == 32) 78 fClusterMSB = B_HOST_TO_LENDIAN_INT16(cluster >> 16); 79 } 80 81 82 void 83 dir_entry::SetSize(uint32 size) 84 { 85 fSize = B_HOST_TO_LENDIAN_INT32(size); 86 } 87 88 89 bool 90 dir_entry::IsFile() const 91 { 92 return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0); 93 } 94 95 96 bool 97 dir_entry::IsDir() const 98 { 99 return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR); 100 } 101 102 103 struct dir_cookie { 104 enum { 105 MAX_UTF16_NAME_LENGTH = 255 106 }; 107 108 int32 index; 109 struct dir_entry entry; 110 off_t entryOffset; 111 uint16 nameBuffer[MAX_UTF16_NAME_LENGTH]; 112 uint32 nameLength; 113 114 off_t Offset() const { return index * sizeof(struct dir_entry); } 115 char* Name() { return (char*)nameBuffer; } 116 117 void ResetName(); 118 bool AddNameChars(const uint16* chars, uint32 count); 119 bool ConvertNameToUTF8(); 120 void Set8_3Name(const char* baseName, const char* extension); 121 }; 122 123 124 void 125 dir_cookie::ResetName() 126 { 127 nameLength = 0; 128 } 129 130 131 bool 132 dir_cookie::AddNameChars(const uint16* chars, uint32 count) 133 { 134 // If there is a null character, we ignore it and all subsequent characters. 135 for (uint32 i = 0; i < count; i++) { 136 if (chars[i] == 0) { 137 count = i; 138 break; 139 } 140 } 141 142 if (count > 0) { 143 if (count > (MAX_UTF16_NAME_LENGTH - nameLength)) 144 return false; 145 146 nameLength += count; 147 memcpy(nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength), 148 chars, count * 2); 149 } 150 151 return true; 152 } 153 154 155 bool 156 dir_cookie::ConvertNameToUTF8() 157 { 158 char name[B_FILE_NAME_LENGTH]; 159 uint32 nameOffset = 0; 160 161 const uint16* utf16 = nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength); 162 163 for (uint32 i = 0; i < nameLength; i++) { 164 uint8 utf8[4]; 165 uint32 count; 166 uint16 c = B_LENDIAN_TO_HOST_INT16(utf16[i]); 167 if (c < 0x80) { 168 utf8[0] = c; 169 count = 1; 170 } else if (c < 0xff80) { 171 utf8[0] = 0xc0 | (c >> 6); 172 utf8[1] = 0x80 | (c & 0x3f); 173 count = 2; 174 } else if ((c & 0xfc00) != 0xd800) { 175 utf8[0] = 0xe0 | (c >> 12); 176 utf8[1] = 0x80 | ((c >> 6) & 0x3f); 177 utf8[2] = 0x80 | (c & 0x3f); 178 count = 3; 179 } else { 180 // surrogate pair 181 if (i + 1 >= nameLength) 182 return false; 183 184 uint16 c2 = B_LENDIAN_TO_HOST_INT16(utf16[++i]); 185 if ((c2 & 0xfc00) != 0xdc00) 186 return false; 187 188 uint32 value = ((c - 0xd7c0) << 10) | (c2 & 0x3ff); 189 utf8[0] = 0xf0 | (value >> 18); 190 utf8[1] = 0x80 | ((value >> 12) & 0x3f); 191 utf8[2] = 0x80 | ((value >> 6) & 0x3f); 192 utf8[3] = 0x80 | (value & 0x3f); 193 count = 4; 194 } 195 196 if (nameOffset + count >= sizeof(name)) 197 return false; 198 199 memcpy(name + nameOffset, utf8, count); 200 nameOffset += count; 201 } 202 203 name[nameOffset] = '\0'; 204 strlcpy(Name(), name, sizeof(nameBuffer)); 205 return true; 206 } 207 208 209 void 210 dir_cookie::Set8_3Name(const char* baseName, const char* extension) 211 { 212 // trim base name 213 uint32 baseNameLength = 8; 214 while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ') 215 baseNameLength--; 216 217 // trim extension 218 uint32 extensionLength = 3; 219 while (extensionLength > 0 && extension[extensionLength - 1] == ' ') 220 extensionLength--; 221 222 // compose the name 223 char* name = Name(); 224 memcpy(name, baseName, baseNameLength); 225 226 if (extensionLength > 0) { 227 name[baseNameLength] = '.'; 228 memcpy(name + baseNameLength + 1, extension, extensionLength); 229 name[baseNameLength + 1 + extensionLength] = '\0'; 230 } else 231 name[baseNameLength] = '\0'; 232 } 233 234 235 // #pragma mark - 236 237 238 static bool 239 is_valid_8_3_file_name_char(char c) 240 { 241 if ((uint8)c >= 128) 242 return true; 243 244 if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) 245 return true; 246 247 return strchr("*!#$%&'()-@^_`{}~ ", c) != NULL; 248 } 249 250 251 static bool 252 check_valid_8_3_file_name(const char* name, const char*& _baseName, 253 uint32& _baseNameLength, const char*& _extension, uint32& _extensionLength) 254 { 255 // check length of base name and extension 256 size_t nameLength = strlen(name); 257 const char* extension = strchr(name, '.'); 258 size_t baseNameLength; 259 size_t extensionLength; 260 if (extension != NULL) { 261 baseNameLength = extension - name; 262 extensionLength = nameLength - baseNameLength - 1; 263 if (extensionLength > 0) 264 extension++; 265 else 266 extension = NULL; 267 } else { 268 baseNameLength = nameLength; 269 extensionLength = 0; 270 } 271 272 // trim trailing space 273 while (baseNameLength > 0 && name[baseNameLength - 1] == ' ') 274 baseNameLength--; 275 while (extensionLength > 0 && extension[extensionLength - 1] == ' ') 276 extensionLength--; 277 278 if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3) 279 return false; 280 281 // check the chars 282 for (size_t i = 0; i < baseNameLength; i++) { 283 if (!is_valid_8_3_file_name_char(name[i])) 284 return false; 285 } 286 287 for (size_t i = 0; i < extensionLength; i++) { 288 if (!is_valid_8_3_file_name_char(extension[i])) 289 return false; 290 } 291 292 _baseName = name; 293 _baseNameLength = baseNameLength; 294 _extension = extension; 295 _extensionLength = extensionLength; 296 297 return true; 298 } 299 300 301 // #pragma mark - Directory 302 303 Directory::Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster, 304 const char *name) 305 : 306 fVolume(volume), 307 fStream(volume, cluster, UINT32_MAX, name), 308 fDirEntryOffset(dirEntryOffset) 309 { 310 TRACE(("FASFS::Directory::(, %lu, %s)\n", cluster, name)); 311 } 312 313 314 Directory::~Directory() 315 { 316 TRACE(("FASFS::Directory::~()\n")); 317 } 318 319 320 status_t 321 Directory::InitCheck() 322 { 323 status_t err; 324 err = fStream.InitCheck(); 325 if (err < B_OK) 326 return err; 327 return B_OK; 328 } 329 330 331 status_t 332 Directory::Open(void **_cookie, int mode) 333 { 334 TRACE(("FASFS::Directory::%s(, %d)\n", __FUNCTION__, mode)); 335 _inherited::Open(_cookie, mode); 336 337 dir_cookie *c = new(nothrow) dir_cookie; 338 if (c == NULL) 339 return B_NO_MEMORY; 340 341 c->index = -1; 342 c->entryOffset = 0; 343 344 *_cookie = (void *)c; 345 return B_OK; 346 } 347 348 349 status_t 350 Directory::Close(void *cookie) 351 { 352 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); 353 _inherited::Close(cookie); 354 355 delete (struct dir_cookie *)cookie; 356 return B_OK; 357 } 358 359 360 Node* 361 Directory::LookupDontTraverse(const char* name) 362 { 363 TRACE(("FASFS::Directory::%s('%s')\n", __FUNCTION__, name)); 364 if (!strcmp(name, ".")) { 365 Acquire(); 366 return this; 367 } 368 369 status_t err; 370 struct dir_cookie cookie; 371 struct dir_cookie *c = &cookie; 372 c->index = -1; 373 c->entryOffset = 0; 374 375 do { 376 err = GetNextEntry(c); 377 if (err < B_OK) 378 return NULL; 379 TRACE(("FASFS::Directory::%s: %s <> '%s'\n", __FUNCTION__, 380 name, c->Name())); 381 if (strcasecmp(name, c->Name()) == 0) { 382 TRACE(("GOT IT!\n")); 383 break; 384 } 385 } while (true); 386 387 if (c->entry.IsFile()) { 388 TRACE(("IS FILE\n")); 389 return new File(fVolume, c->entryOffset, 390 c->entry.Cluster(fVolume.FatBits()), c->entry.Size(), name); 391 } 392 if (c->entry.IsDir()) { 393 TRACE(("IS DIR\n")); 394 return new Directory(fVolume, c->entryOffset, 395 c->entry.Cluster(fVolume.FatBits()), name); 396 } 397 return NULL; 398 } 399 400 401 status_t 402 Directory::GetNextEntry(void *cookie, char *name, size_t size) 403 { 404 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); 405 struct dir_cookie *c = (struct dir_cookie *)cookie; 406 status_t err; 407 408 err = GetNextEntry(cookie); 409 if (err < B_OK) 410 return err; 411 412 strlcpy(name, c->Name(), size); 413 return B_OK; 414 } 415 416 417 status_t 418 Directory::GetNextNode(void *cookie, Node **_node) 419 { 420 return B_ERROR; 421 } 422 423 424 status_t 425 Directory::Rewind(void *cookie) 426 { 427 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); 428 struct dir_cookie *c = (struct dir_cookie *)cookie; 429 c->index = -1; 430 c->entryOffset = 0; 431 432 return B_OK; 433 } 434 435 436 bool 437 Directory::IsEmpty() 438 { 439 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); 440 struct dir_cookie cookie; 441 struct dir_cookie *c = &cookie; 442 c->index = -1; 443 c->entryOffset = 0; 444 if (GetNextEntry(c) == B_OK) 445 return false; 446 return true; 447 } 448 449 450 status_t 451 Directory::GetName(char *name, size_t size) const 452 { 453 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); 454 if (this == fVolume.Root()) 455 return fVolume.GetName(name, size); 456 return fStream.GetName(name, size); 457 } 458 459 460 ino_t 461 Directory::Inode() const 462 { 463 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__)); 464 return fStream.FirstCluster() << 16; 465 } 466 467 468 status_t 469 Directory::CreateFile(const char* name, mode_t permissions, Node** _node) 470 { 471 if (Node* node = Lookup(name, false)) { 472 node->Release(); 473 return B_FILE_EXISTS; 474 } 475 476 // We only support 8.3 file names ATM. 477 const char* baseName; 478 const char* extension; 479 uint32 baseNameLength; 480 uint32 extensionLength; 481 if (!check_valid_8_3_file_name(name, baseName, baseNameLength, extension, 482 extensionLength)) { 483 return B_UNSUPPORTED; 484 } 485 486 // prepare a directory entry for the new file 487 dir_entry entry; 488 489 memset(entry.fName, ' ', sizeof(entry.fName)); 490 memset(entry.fExt, ' ', sizeof(entry.fExt)); 491 // clear both base name and extension 492 memcpy(entry.fName, baseName, baseNameLength); 493 if (extensionLength > 0) 494 memcpy(entry.fExt, extension, extensionLength); 495 496 entry.fFlags = 0; 497 entry.fReserved1 = 0; 498 entry.fCreateTime10ms = 199; 499 entry.fCreateTime = B_HOST_TO_LENDIAN_INT16((23 << 11) | (59 << 5) | 29); 500 // 23:59:59.9 501 entry.fCreateDate = B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31); 502 // 2107-12-31 503 entry.fAccessDate = entry.fCreateDate; 504 entry.fClusterMSB = 0; 505 entry.fModifiedTime = entry.fCreateTime; 506 entry.fModifiedDate = entry.fCreateDate; 507 entry.fClusterLSB = 0; 508 entry.fSize = 0; 509 510 // add the entry to the directory 511 off_t entryOffset; 512 status_t error = _AddEntry(entry, entryOffset); 513 if (error != B_OK) 514 return error; 515 516 // create a File object 517 File* file = new(nothrow) File(fVolume, entryOffset, 518 entry.Cluster(fVolume.FatBits()), entry.Size(), name); 519 if (file == NULL) 520 return B_NO_MEMORY; 521 522 *_node = file; 523 return B_OK; 524 } 525 526 527 /*static*/ status_t 528 Directory::UpdateDirEntry(Volume& volume, off_t dirEntryOffset, 529 uint32 firstCluster, uint32 size) 530 { 531 if (dirEntryOffset == 0) 532 return B_BAD_VALUE; 533 534 CachedBlock cachedBlock(volume); 535 off_t block = volume.ToBlock(dirEntryOffset); 536 537 status_t error = cachedBlock.SetTo(block, CachedBlock::READ); 538 if (error != B_OK) 539 return error; 540 541 dir_entry* entry = (dir_entry*)(cachedBlock.Block() 542 + dirEntryOffset % volume.BlockSize()); 543 544 entry->SetCluster(firstCluster, volume.FatBits()); 545 entry->SetSize(size); 546 547 return cachedBlock.Flush(); 548 } 549 550 551 status_t 552 Directory::GetNextEntry(void *cookie, uint8 mask, uint8 match) 553 { 554 TRACE(("FASFS::Directory::%s(, %02x, %02x)\n", __FUNCTION__, mask, match)); 555 struct dir_cookie *c = (struct dir_cookie *)cookie; 556 557 bool longNameValid = false; 558 559 do { 560 c->index++; 561 size_t len = sizeof(c->entry); 562 if (fStream.ReadAt(c->Offset(), (uint8 *)&c->entry, &len, 563 &c->entryOffset) != B_OK || len != sizeof(c->entry)) { 564 return B_ENTRY_NOT_FOUND; 565 } 566 567 TRACE(("FASFS::Directory::%s: got one entry\n", __FUNCTION__)); 568 if ((uint8)c->entry.fName[0] == 0x00) // last one 569 return B_ENTRY_NOT_FOUND; 570 if ((uint8)c->entry.fName[0] == 0xe5) // deleted 571 continue; 572 if (c->entry.Flags() == 0x0f) { // LFN entry 573 uint8* nameEntry = (uint8*)&c->entry; 574 if ((*nameEntry & 0x40) != 0) { 575 c->ResetName(); 576 longNameValid = true; 577 } 578 579 uint16 nameChars[13]; 580 memcpy(nameChars, nameEntry + 0x01, 10); 581 memcpy(nameChars + 5, nameEntry + 0x0e, 12); 582 memcpy(nameChars + 11, nameEntry + 0x1c, 4); 583 longNameValid |= c->AddNameChars(nameChars, 13); 584 continue; 585 } 586 if ((c->entry.Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_VOLUME) { 587 // TODO handle Volume name (set fVolume's name) 588 continue; 589 } 590 TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__, 591 c->entry.BaseName(), c->entry.Extension(), c->entry.Flags())); 592 if ((c->entry.Flags() & mask) == match) { 593 if (longNameValid) 594 longNameValid = c->ConvertNameToUTF8(); 595 if (!longNameValid) { 596 // copy 8.3 name to name buffer 597 c->Set8_3Name(c->entry.BaseName(), c->entry.Extension()); 598 } 599 break; 600 } 601 } while (true); 602 TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__, 603 c->entry.BaseName(), c->entry.Extension())); 604 return B_OK; 605 } 606 607 608 status_t 609 Directory::_AddEntry(dir_entry& entry, off_t& _entryOffset) 610 { 611 off_t dirSize = _GetStreamSize(); 612 if (dirSize < 0) 613 return dirSize; 614 615 uint32 firstCluster = fStream.FirstCluster(); 616 617 // First null-terminate the new entry list, so we don't leave the list in 618 // a broken state, if writing the actual entry fails. We only need to do 619 // that when the entry is not at the end of a cluster. 620 if ((dirSize + sizeof(entry)) % fVolume.ClusterSize() != 0) { 621 // TODO: Rather zero the complete remainder of the cluster? 622 size_t size = 1; 623 char terminator = 0; 624 status_t error = fStream.WriteAt(dirSize + sizeof(entry), &terminator, 625 &size); 626 if (error != B_OK) 627 return error; 628 if (size != 1) 629 return B_ERROR; 630 } 631 632 // write the entry 633 size_t size = sizeof(entry); 634 status_t error = fStream.WriteAt(dirSize, &entry, &size, &_entryOffset); 635 if (error != B_OK) 636 return error; 637 if (size != sizeof(entry)) 638 return B_ERROR; 639 // TODO: Undo changes! 640 641 fStream.SetSize(dirSize + sizeof(entry)); 642 643 // If the directory cluster has changed (which should only happen, if the 644 // directory was empty before), we need to adjust the directory entry. 645 if (firstCluster != fStream.FirstCluster()) { 646 error = UpdateDirEntry(fVolume, fDirEntryOffset, fStream.FirstCluster(), 647 0); 648 if (error != B_OK) 649 return error; 650 // TODO: Undo changes! 651 } 652 653 return B_OK; 654 } 655 656 657 off_t 658 Directory::_GetStreamSize() 659 { 660 off_t size = fStream.Size(); 661 if (size != UINT32_MAX) 662 return size; 663 664 // iterate to the end of the directory 665 size = 0; 666 while (true) { 667 dir_entry entry; 668 size_t entrySize = sizeof(entry); 669 status_t error = fStream.ReadAt(size, &entry, &entrySize); 670 if (error != B_OK) 671 return error; 672 673 if (entrySize != sizeof(entry) || entry.fName[0] == 0) 674 break; 675 676 size += sizeof(entry); 677 } 678 679 fStream.SetSize(size); 680 return size; 681 } 682 683 684 } // namespace FATFS 685