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