1 /* 2 * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de. 3 * Copyright 2005-2013, Axel Dörfler, axeld@pinc-software.de. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "tarfs.h" 10 11 #include <fcntl.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <unistd.h> 16 17 #include <AutoDeleter.h> 18 #include <OS.h> 19 #include <SupportDefs.h> 20 21 #include <zlib.h> 22 23 #include <boot/partitions.h> 24 #include <boot/platform.h> 25 #include <util/DoublyLinkedList.h> 26 27 28 //#define TRACE_TARFS 29 #ifdef TRACE_TARFS 30 # define TRACE(x) dprintf x 31 #else 32 # define TRACE(x) ; 33 #endif 34 35 36 static const uint32 kFloppyArchiveOffset = BOOT_ARCHIVE_IMAGE_OFFSET * 1024; 37 // defined at build time, see build/jam/BuildSetup 38 static const size_t kTarRegionSize = 8 * 1024 * 1024; // 8 MB 39 40 41 using std::nothrow; 42 43 44 namespace TarFS { 45 46 47 struct RegionDelete { 48 inline void operator()(void* memory) 49 { 50 if (memory != NULL) 51 platform_free_region(memory, kTarRegionSize); 52 } 53 }; 54 55 struct RegionDeleter : BPrivate::AutoDeleter<void, RegionDelete> { 56 RegionDeleter() 57 : 58 BPrivate::AutoDeleter<void, RegionDelete>() 59 { 60 } 61 62 RegionDeleter(void* memory) 63 : 64 BPrivate::AutoDeleter<void, RegionDelete>(memory) 65 { 66 } 67 }; 68 69 class Directory; 70 71 class Entry : public DoublyLinkedListLinkImpl<Entry> { 72 public: 73 Entry(const char* name); 74 virtual ~Entry() {} 75 76 const char* Name() const { return fName; } 77 virtual ::Node* ToNode() = 0; 78 virtual TarFS::Directory* ToTarDirectory() { return NULL; } 79 80 protected: 81 const char* fName; 82 int32 fID; 83 }; 84 85 86 typedef DoublyLinkedList<TarFS::Entry> EntryList; 87 typedef EntryList::Iterator EntryIterator; 88 89 90 class File : public ::Node, public Entry { 91 public: 92 File(tar_header* header, const char* name); 93 virtual ~File(); 94 95 virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer, 96 size_t bufferSize); 97 virtual ssize_t WriteAt(void* cookie, off_t pos, 98 const void* buffer, size_t bufferSize); 99 100 virtual status_t GetName(char* nameBuffer, 101 size_t bufferSize) const; 102 103 virtual int32 Type() const; 104 virtual off_t Size() const; 105 virtual ino_t Inode() const; 106 107 virtual ::Node* ToNode() { return this; } 108 109 private: 110 tar_header* fHeader; 111 off_t fSize; 112 }; 113 114 115 class Directory : public ::Directory, public Entry { 116 public: 117 Directory(Directory* parent, const char* name); 118 virtual ~Directory(); 119 120 virtual status_t Open(void** _cookie, int mode); 121 virtual status_t Close(void* cookie); 122 123 virtual status_t GetName(char* nameBuffer, 124 size_t bufferSize) const; 125 126 virtual TarFS::Entry* LookupEntry(const char* name); 127 virtual ::Node* LookupDontTraverse(const char* name); 128 129 virtual status_t GetNextEntry(void* cookie, char* nameBuffer, 130 size_t bufferSize); 131 virtual status_t GetNextNode(void* cookie, Node** _node); 132 virtual status_t Rewind(void* cookie); 133 virtual bool IsEmpty(); 134 135 virtual ino_t Inode() const; 136 137 virtual ::Node* ToNode() { return this; }; 138 virtual TarFS::Directory* ToTarDirectory() { return this; } 139 140 status_t AddDirectory(char* dirName, 141 TarFS::Directory** _dir = NULL); 142 status_t AddFile(tar_header* header); 143 144 private: 145 typedef ::Directory _inherited; 146 147 Directory* fParent; 148 EntryList fEntries; 149 }; 150 151 152 class Symlink : public ::Node, public Entry { 153 public: 154 Symlink(tar_header* header, const char* name); 155 virtual ~Symlink(); 156 157 virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer, 158 size_t bufferSize); 159 virtual ssize_t WriteAt(void* cookie, off_t pos, 160 const void* buffer, size_t bufferSize); 161 162 virtual status_t ReadLink(char* buffer, size_t bufferSize); 163 164 virtual status_t GetName(char* nameBuffer, 165 size_t bufferSize) const; 166 167 virtual int32 Type() const; 168 virtual off_t Size() const; 169 virtual ino_t Inode() const; 170 171 const char* LinkPath() const { return fHeader->linkname; } 172 173 virtual ::Node* ToNode() { return this; } 174 175 private: 176 tar_header* fHeader; 177 size_t fSize; 178 }; 179 180 181 class Volume : public TarFS::Directory { 182 public: 183 Volume(); 184 ~Volume(); 185 186 status_t Init(boot::Partition* partition); 187 188 TarFS::Directory* Root() { return this; } 189 190 private: 191 status_t _Inflate(boot::Partition* partition, 192 void* cookie, off_t offset, 193 RegionDeleter& regionDeleter, 194 size_t* inflatedBytes); 195 }; 196 197 } // namespace TarFS 198 199 200 static int32 sNextID = 1; 201 202 203 // #pragma mark - 204 205 206 bool 207 skip_gzip_header(z_stream* stream) 208 { 209 uint8* buffer = (uint8*)stream->next_in; 210 211 // check magic and skip method 212 if (buffer[0] != 0x1f || buffer[1] != 0x8b) 213 return false; 214 215 // we need the flags field to determine the length of the header 216 int flags = buffer[3]; 217 218 uint32 offset = 10; 219 220 if ((flags & 0x04) != 0) { 221 // skip extra field 222 offset += (buffer[offset] | (buffer[offset + 1] << 8)) + 2; 223 if (offset >= stream->avail_in) 224 return false; 225 } 226 if ((flags & 0x08) != 0) { 227 // skip original name 228 while (buffer[offset++]) 229 ; 230 } 231 if ((flags & 0x10) != 0) { 232 // skip comment 233 while (buffer[offset++]) 234 ; 235 } 236 if ((flags & 0x02) != 0) { 237 // skip CRC 238 offset += 2; 239 } 240 241 if (offset >= stream->avail_in) 242 return false; 243 244 stream->next_in += offset; 245 stream->avail_in -= offset; 246 return true; 247 } 248 249 250 // #pragma mark - 251 252 253 TarFS::Entry::Entry(const char* name) 254 : 255 fName(name), 256 fID(sNextID++) 257 { 258 } 259 260 261 // #pragma mark - 262 263 264 TarFS::File::File(tar_header* header, const char* name) 265 : TarFS::Entry(name), 266 fHeader(header) 267 { 268 fSize = strtol(header->size, NULL, 8); 269 } 270 271 272 TarFS::File::~File() 273 { 274 } 275 276 277 ssize_t 278 TarFS::File::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize) 279 { 280 TRACE(("tarfs: read at %" B_PRIdOFF ", %" B_PRIuSIZE " bytes, fSize = %" 281 B_PRIdOFF "\n", pos, bufferSize, fSize)); 282 283 if (pos < 0 || !buffer) 284 return B_BAD_VALUE; 285 286 if (pos >= fSize || bufferSize == 0) 287 return 0; 288 289 size_t toRead = fSize - pos; 290 if (toRead > bufferSize) 291 toRead = bufferSize; 292 293 memcpy(buffer, (char*)fHeader + BLOCK_SIZE + pos, toRead); 294 295 return toRead; 296 } 297 298 299 ssize_t 300 TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer, 301 size_t bufferSize) 302 { 303 return B_NOT_ALLOWED; 304 } 305 306 307 status_t 308 TarFS::File::GetName(char* nameBuffer, size_t bufferSize) const 309 { 310 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize 311 ? B_BUFFER_OVERFLOW : B_OK; 312 } 313 314 315 int32 316 TarFS::File::Type() const 317 { 318 return S_IFREG; 319 } 320 321 322 off_t 323 TarFS::File::Size() const 324 { 325 return fSize; 326 } 327 328 329 ino_t 330 TarFS::File::Inode() const 331 { 332 return fID; 333 } 334 335 336 // #pragma mark - 337 338 TarFS::Directory::Directory(Directory* parent, const char* name) 339 : 340 TarFS::Entry(name), 341 fParent(parent) 342 { 343 } 344 345 346 TarFS::Directory::~Directory() 347 { 348 while (TarFS::Entry* entry = fEntries.Head()) { 349 fEntries.Remove(entry); 350 delete entry; 351 } 352 } 353 354 355 status_t 356 TarFS::Directory::Open(void** _cookie, int mode) 357 { 358 _inherited::Open(_cookie, mode); 359 360 EntryIterator* iterator 361 = new(nothrow) EntryIterator(fEntries.GetIterator()); 362 if (iterator == NULL) 363 return B_NO_MEMORY; 364 365 *_cookie = iterator; 366 367 return B_OK; 368 } 369 370 371 status_t 372 TarFS::Directory::Close(void* cookie) 373 { 374 _inherited::Close(cookie); 375 376 delete (EntryIterator*)cookie; 377 return B_OK; 378 } 379 380 381 status_t 382 TarFS::Directory::GetName(char* nameBuffer, size_t bufferSize) const 383 { 384 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize 385 ? B_BUFFER_OVERFLOW : B_OK; 386 } 387 388 389 TarFS::Entry* 390 TarFS::Directory::LookupEntry(const char* name) 391 { 392 if (strcmp(name, ".") == 0) 393 return this; 394 if (strcmp(name, "..") == 0) 395 return fParent; 396 397 EntryIterator iterator(fEntries.GetIterator()); 398 399 while (iterator.HasNext()) { 400 TarFS::Entry* entry = iterator.Next(); 401 if (strcmp(name, entry->Name()) == 0) 402 return entry; 403 } 404 405 return NULL; 406 } 407 408 409 ::Node* 410 TarFS::Directory::LookupDontTraverse(const char* name) 411 { 412 TarFS::Entry* entry = LookupEntry(name); 413 if (!entry) 414 return NULL; 415 416 Node* node = entry->ToNode(); 417 if (node) 418 node->Acquire(); 419 420 return node; 421 } 422 423 424 status_t 425 TarFS::Directory::GetNextEntry(void* _cookie, char* name, size_t size) 426 { 427 EntryIterator* iterator = (EntryIterator*)_cookie; 428 TarFS::Entry* entry = iterator->Next(); 429 430 if (entry != NULL) { 431 strlcpy(name, entry->Name(), size); 432 return B_OK; 433 } 434 435 return B_ENTRY_NOT_FOUND; 436 } 437 438 439 status_t 440 TarFS::Directory::GetNextNode(void* _cookie, Node** _node) 441 { 442 EntryIterator* iterator = (EntryIterator*)_cookie; 443 TarFS::Entry* entry = iterator->Next(); 444 445 if (entry != NULL) { 446 *_node = entry->ToNode(); 447 return B_OK; 448 } 449 return B_ENTRY_NOT_FOUND; 450 } 451 452 453 status_t 454 TarFS::Directory::Rewind(void* _cookie) 455 { 456 EntryIterator* iterator = (EntryIterator*)_cookie; 457 *iterator = fEntries.GetIterator(); 458 return B_OK; 459 } 460 461 462 status_t 463 TarFS::Directory::AddDirectory(char* dirName, TarFS::Directory** _dir) 464 { 465 char* subDir = strchr(dirName, '/'); 466 if (subDir) { 467 // skip slashes 468 while (*subDir == '/') { 469 *subDir = '\0'; 470 subDir++; 471 } 472 473 if (*subDir == '\0') { 474 // a trailing slash 475 subDir = NULL; 476 } 477 } 478 479 // check, whether the directory does already exist 480 Entry* entry = LookupEntry(dirName); 481 TarFS::Directory* dir = (entry ? entry->ToTarDirectory() : NULL); 482 if (entry) { 483 if (!dir) 484 return B_ERROR; 485 } else { 486 // doesn't exist yet -- create it 487 dir = new(nothrow) TarFS::Directory(this, dirName); 488 if (!dir) 489 return B_NO_MEMORY; 490 491 fEntries.Add(dir); 492 } 493 494 // recursively create the subdirectories 495 if (subDir) { 496 status_t error = dir->AddDirectory(subDir, &dir); 497 if (error != B_OK) 498 return error; 499 } 500 501 if (_dir) 502 *_dir = dir; 503 504 return B_OK; 505 } 506 507 508 status_t 509 TarFS::Directory::AddFile(tar_header* header) 510 { 511 char* leaf = strrchr(header->name, '/'); 512 char* dirName = NULL; 513 if (leaf) { 514 dirName = header->name; 515 *leaf = '\0'; 516 leaf++; 517 } else 518 leaf = header->name; 519 520 // create the parent directory 521 TarFS::Directory* dir = this; 522 if (dirName) { 523 status_t error = AddDirectory(dirName, &dir); 524 if (error != B_OK) 525 return error; 526 } 527 528 // create the entry 529 TarFS::Entry* entry; 530 if (header->type == TAR_FILE || header->type == TAR_FILE2) 531 entry = new(nothrow) TarFS::File(header, leaf); 532 else if (header->type == TAR_SYMLINK) 533 entry = new(nothrow) TarFS::Symlink(header, leaf); 534 else 535 return B_BAD_VALUE; 536 537 if (!entry) 538 return B_NO_MEMORY; 539 540 dir->fEntries.Add(entry); 541 542 return B_OK; 543 } 544 545 546 bool 547 TarFS::Directory::IsEmpty() 548 { 549 return fEntries.IsEmpty(); 550 } 551 552 553 ino_t 554 TarFS::Directory::Inode() const 555 { 556 return fID; 557 } 558 559 560 // #pragma mark - 561 562 563 TarFS::Symlink::Symlink(tar_header* header, const char* name) 564 : TarFS::Entry(name), 565 fHeader(header) 566 { 567 fSize = strnlen(header->linkname, sizeof(header->linkname)); 568 // null-terminate for sure (might overwrite a byte of the magic) 569 header->linkname[fSize++] = '\0'; 570 } 571 572 573 TarFS::Symlink::~Symlink() 574 { 575 } 576 577 578 ssize_t 579 TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize) 580 { 581 return B_NOT_ALLOWED; 582 } 583 584 585 ssize_t 586 TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer, 587 size_t bufferSize) 588 { 589 return B_NOT_ALLOWED; 590 } 591 592 593 status_t 594 TarFS::Symlink::ReadLink(char* buffer, size_t bufferSize) 595 { 596 const char* path = fHeader->linkname; 597 size_t size = strlen(path) + 1; 598 599 if (size > bufferSize) 600 return B_BUFFER_OVERFLOW; 601 602 memcpy(buffer, path, size); 603 return B_OK; 604 } 605 606 607 status_t 608 TarFS::Symlink::GetName(char* nameBuffer, size_t bufferSize) const 609 { 610 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize 611 ? B_BUFFER_OVERFLOW : B_OK; 612 } 613 614 615 int32 616 TarFS::Symlink::Type() const 617 { 618 return S_IFLNK; 619 } 620 621 622 off_t 623 TarFS::Symlink::Size() const 624 { 625 return fSize; 626 } 627 628 629 ino_t 630 TarFS::Symlink::Inode() const 631 { 632 return fID; 633 } 634 635 636 // #pragma mark - 637 638 639 TarFS::Volume::Volume() 640 : 641 TarFS::Directory(this, "Boot from CD-ROM") 642 { 643 } 644 645 646 TarFS::Volume::~Volume() 647 { 648 } 649 650 651 status_t 652 TarFS::Volume::Init(boot::Partition* partition) 653 { 654 void* cookie; 655 status_t error = partition->Open(&cookie, O_RDONLY); 656 if (error != B_OK) 657 return error; 658 659 struct PartitionCloser { 660 boot::Partition *partition; 661 void *cookie; 662 663 PartitionCloser(boot::Partition* partition, void* cookie) 664 : partition(partition), 665 cookie(cookie) 666 { 667 } 668 669 ~PartitionCloser() 670 { 671 partition->Close(cookie); 672 } 673 } _(partition, cookie); 674 675 // inflate the tar file -- try offset 0 and the archive offset on a floppy 676 // disk 677 RegionDeleter regionDeleter; 678 size_t inflatedBytes; 679 status_t status = _Inflate(partition, cookie, 0, regionDeleter, 680 &inflatedBytes); 681 if (status != B_OK) { 682 status = _Inflate(partition, cookie, kFloppyArchiveOffset, 683 regionDeleter, &inflatedBytes); 684 } 685 if (status != B_OK) 686 return status; 687 688 // parse the tar file 689 char* block = (char*)regionDeleter.Get(); 690 int blockCount = inflatedBytes / BLOCK_SIZE; 691 int blockIndex = 0; 692 693 while (blockIndex < blockCount) { 694 // check header 695 tar_header* header = (tar_header*)(block + blockIndex * BLOCK_SIZE); 696 //dump_header(*header); 697 698 if (header->magic[0] == '\0') 699 break; 700 701 if (strcmp(header->magic, kTarHeaderMagic) != 0) { 702 if (strcmp(header->magic, kOldTarHeaderMagic) != 0) { 703 dprintf("Bad tar header magic in block %d.\n", blockIndex); 704 status = B_BAD_DATA; 705 break; 706 } 707 } 708 709 off_t size = strtol(header->size, NULL, 8); 710 711 TRACE(("tarfs: \"%s\", %" B_PRIdOFF " bytes\n", header->name, size)); 712 713 // TODO: this is old-style GNU tar which probably won't work with newer 714 // ones... 715 switch (header->type) { 716 case TAR_FILE: 717 case TAR_FILE2: 718 case TAR_SYMLINK: 719 status = AddFile(header); 720 break; 721 722 case TAR_DIRECTORY: 723 status = AddDirectory(header->name, NULL); 724 break; 725 726 case TAR_LONG_NAME: 727 // this is a long file name 728 // TODO: read long name 729 default: 730 dprintf("tarfs: unsupported file type: %d ('%c')\n", 731 header->type, header->type); 732 // unsupported type 733 status = B_ERROR; 734 break; 735 } 736 737 if (status != B_OK) 738 return status; 739 740 // next block 741 blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE; 742 } 743 744 if (status != B_OK) 745 return status; 746 747 regionDeleter.Detach(); 748 return B_OK; 749 } 750 751 752 status_t 753 TarFS::Volume::_Inflate(boot::Partition* partition, void* cookie, off_t offset, 754 RegionDeleter& regionDeleter, size_t* inflatedBytes) 755 { 756 char in[2048]; 757 z_stream zStream = { 758 (Bytef*)in, // next in 759 sizeof(in), // avail in 760 0, // total in 761 NULL, // next out 762 0, // avail out 763 0, // total out 764 0, // msg 765 0, // state 766 Z_NULL, // zalloc 767 Z_NULL, // zfree 768 Z_NULL, // opaque 769 0, // data type 770 0, // adler 771 0, // reserved 772 }; 773 774 int status; 775 char* out = (char*)regionDeleter.Get(); 776 bool headerRead = false; 777 778 do { 779 ssize_t bytesRead = partition->ReadAt(cookie, offset, in, sizeof(in)); 780 if (bytesRead != (ssize_t)sizeof(in)) { 781 if (bytesRead <= 0) { 782 status = Z_STREAM_ERROR; 783 break; 784 } 785 } 786 787 zStream.avail_in = bytesRead; 788 zStream.next_in = (Bytef*)in; 789 790 if (!headerRead) { 791 // check and skip gzip header 792 if (!skip_gzip_header(&zStream)) 793 return B_BAD_DATA; 794 headerRead = true; 795 796 if (!out) { 797 // allocate memory for the uncompressed data 798 if (platform_allocate_region((void**)&out, kTarRegionSize, 799 B_READ_AREA | B_WRITE_AREA, false) != B_OK) { 800 TRACE(("tarfs: allocating region failed!\n")); 801 return B_NO_MEMORY; 802 } 803 regionDeleter.SetTo(out); 804 } 805 806 zStream.avail_out = kTarRegionSize; 807 zStream.next_out = (Bytef*)out; 808 809 status = inflateInit2(&zStream, -15); 810 if (status != Z_OK) 811 return B_ERROR; 812 } 813 814 status = inflate(&zStream, Z_SYNC_FLUSH); 815 offset += bytesRead; 816 817 if (zStream.avail_in != 0 && status != Z_STREAM_END) 818 dprintf("tarfs: didn't read whole block: %s\n", zStream.msg); 819 } while (status == Z_OK); 820 821 inflateEnd(&zStream); 822 823 if (status != Z_STREAM_END) { 824 TRACE(("tarfs: inflating failed: %d!\n", status)); 825 return B_BAD_DATA; 826 } 827 828 *inflatedBytes = zStream.total_out; 829 830 return B_OK; 831 } 832 833 834 // #pragma mark - 835 836 837 static status_t 838 tarfs_get_file_system(boot::Partition* partition, ::Directory** _root) 839 { 840 TarFS::Volume* volume = new(nothrow) TarFS::Volume; 841 if (volume == NULL) 842 return B_NO_MEMORY; 843 844 if (volume->Init(partition) < B_OK) { 845 TRACE(("Initializing tarfs failed\n")); 846 delete volume; 847 return B_ERROR; 848 } 849 850 *_root = volume->Root(); 851 return B_OK; 852 } 853 854 855 file_system_module_info gTarFileSystemModule = { 856 "file_systems/tarfs/v1", 857 kPartitionTypeTarFS, 858 NULL, // identify_file_system 859 tarfs_get_file_system 860 }; 861 862