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