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