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