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