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