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