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 36static const uint32 kFloppyArchiveOffset = BOOT_ARCHIVE_IMAGE_OFFSET * 1024; 37 // defined at build time, see build/jam/BuildSetup 38static const size_t kTarRegionSize = 9 * 1024 * 1024; // 9 MB 39 40 41using std::nothrow; 42 43 44namespace TarFS { 45 46 47struct RegionDelete { 48 inline void operator()(void* memory) 49 { 50 if (memory != NULL) 51 platform_free_region(memory, kTarRegionSize); 52 } 53}; 54 55typedef BPrivate::AutoDeleter<void, RegionDelete> RegionDeleter; 56 57class Directory; 58 59class Entry : public DoublyLinkedListLinkImpl<Entry> { 60public: 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 68protected: 69 const char* fName; 70 int32 fID; 71}; 72 73 74typedef DoublyLinkedList<TarFS::Entry> EntryList; 75typedef EntryList::Iterator EntryIterator; 76 77 78class File : public ::Node, public Entry { 79public: 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 97private: 98 tar_header* fHeader; 99 off_t fSize; 100}; 101 102 103class Directory : public ::Directory, public Entry { 104public: 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 132private: 133 typedef ::Directory _inherited; 134 135 Directory* fParent; 136 EntryList fEntries; 137}; 138 139 140class Symlink : public ::Node, public Entry { 141public: 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 163private: 164 tar_header* fHeader; 165 size_t fSize; 166}; 167 168 169class Volume : public TarFS::Directory { 170public: 171 Volume(); 172 ~Volume(); 173 174 status_t Init(boot::Partition* partition); 175 176 TarFS::Directory* Root() { return this; } 177 178private: 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 188static int32 sNextID = 1; 189 190 191// #pragma mark - 192 193 194bool 195skip_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 241TarFS::Entry::Entry(const char* name) 242 : 243 fName(name), 244 fID(sNextID++) 245{ 246} 247 248 249// #pragma mark - 250 251 252TarFS::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 260TarFS::File::~File() 261{ 262} 263 264 265ssize_t 266TarFS::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 287ssize_t 288TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer, 289 size_t bufferSize) 290{ 291 return B_NOT_ALLOWED; 292} 293 294 295status_t 296TarFS::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 303int32 304TarFS::File::Type() const 305{ 306 return S_IFREG; 307} 308 309 310off_t 311TarFS::File::Size() const 312{ 313 return fSize; 314} 315 316 317ino_t 318TarFS::File::Inode() const 319{ 320 return fID; 321} 322 323 324// #pragma mark - 325 326TarFS::Directory::Directory(Directory* parent, const char* name) 327 : 328 TarFS::Entry(name), 329 fParent(parent) 330{ 331} 332 333 334TarFS::Directory::~Directory() 335{ 336 while (TarFS::Entry* entry = fEntries.Head()) { 337 fEntries.Remove(entry); 338 delete entry; 339 } 340} 341 342 343status_t 344TarFS::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 359status_t 360TarFS::Directory::Close(void* cookie) 361{ 362 _inherited::Close(cookie); 363 364 delete (EntryIterator*)cookie; 365 return B_OK; 366} 367 368 369status_t 370TarFS::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 377TarFS::Entry* 378TarFS::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* 398TarFS::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 412status_t 413TarFS::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 427status_t 428TarFS::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 441status_t 442TarFS::Directory::Rewind(void* _cookie) 443{ 444 EntryIterator* iterator = (EntryIterator*)_cookie; 445 *iterator = fEntries.GetIterator(); 446 return B_OK; 447} 448 449 450status_t 451TarFS::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 496status_t 497TarFS::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 534bool 535TarFS::Directory::IsEmpty() 536{ 537 return fEntries.IsEmpty(); 538} 539 540 541ino_t 542TarFS::Directory::Inode() const 543{ 544 return fID; 545} 546 547 548// #pragma mark - 549 550 551TarFS::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 561TarFS::Symlink::~Symlink() 562{ 563} 564 565 566ssize_t 567TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize) 568{ 569 return B_NOT_ALLOWED; 570} 571 572 573ssize_t 574TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer, 575 size_t bufferSize) 576{ 577 return B_NOT_ALLOWED; 578} 579 580 581status_t 582TarFS::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 595status_t 596TarFS::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 603int32 604TarFS::Symlink::Type() const 605{ 606 return S_IFLNK; 607} 608 609 610off_t 611TarFS::Symlink::Size() const 612{ 613 return fSize; 614} 615 616 617ino_t 618TarFS::Symlink::Inode() const 619{ 620 return fID; 621} 622 623 624// #pragma mark - 625 626 627TarFS::Volume::Volume() 628 : 629 TarFS::Directory(this, "Boot from CD-ROM") 630{ 631} 632 633 634TarFS::Volume::~Volume() 635{ 636} 637 638 639status_t 640TarFS::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 740status_t 741TarFS::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 829static status_t 830tarfs_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 847file_system_module_info gTarFileSystemModule = { 848 "file_systems/tarfs/v1", 849 kPartitionTypeTarFS, 850 NULL, // identify_file_system 851 tarfs_get_file_system 852}; 853 854