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