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 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 = 16 * 1024 * 1024; // 16 MB 39 40 namespace TarFS { 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* 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 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 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 167 private: 168 tar_header* fHeader; 169 size_t fSize; 170 }; 171 172 173 class Volume : public TarFS::Directory { 174 public: 175 Volume(); 176 ~Volume(); 177 178 status_t Init(boot::Partition* partition); 179 180 TarFS::Directory* Root() { return this; } 181 182 private: 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 192 static int32 sNextID = 1; 193 194 195 // #pragma mark - 196 197 198 bool 199 skip_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 245 TarFS::Entry::Entry(const char* name) 246 : 247 fName(name), 248 fID(sNextID++) 249 { 250 } 251 252 253 // #pragma mark - 254 255 256 TarFS::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 264 TarFS::File::~File() 265 { 266 } 267 268 269 ssize_t 270 TarFS::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 291 ssize_t 292 TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer, 293 size_t bufferSize) 294 { 295 return B_NOT_ALLOWED; 296 } 297 298 299 status_t 300 TarFS::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 307 int32 308 TarFS::File::Type() const 309 { 310 return S_IFREG; 311 } 312 313 314 off_t 315 TarFS::File::Size() const 316 { 317 return fSize; 318 } 319 320 321 ino_t 322 TarFS::File::Inode() const 323 { 324 return fID; 325 } 326 327 328 // #pragma mark - 329 330 TarFS::Directory::Directory(const char* name) 331 : TarFS::Entry(name) 332 { 333 } 334 335 336 TarFS::Directory::~Directory() 337 { 338 while (TarFS::Entry* entry = fEntries.Head()) { 339 fEntries.Remove(entry); 340 delete entry; 341 } 342 } 343 344 345 status_t 346 TarFS::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 361 status_t 362 TarFS::Directory::Close(void* cookie) 363 { 364 _inherited::Close(cookie); 365 366 delete (EntryIterator*)cookie; 367 return B_OK; 368 } 369 370 371 status_t 372 TarFS::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 379 TarFS::Entry* 380 TarFS::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* 395 TarFS::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 421 status_t 422 TarFS::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 436 status_t 437 TarFS::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 450 status_t 451 TarFS::Directory::Rewind(void* _cookie) 452 { 453 EntryIterator* iterator = (EntryIterator*)_cookie; 454 *iterator = fEntries.GetIterator(); 455 return B_OK; 456 } 457 458 459 status_t 460 TarFS::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 505 status_t 506 TarFS::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 543 bool 544 TarFS::Directory::IsEmpty() 545 { 546 return fEntries.IsEmpty(); 547 } 548 549 550 ino_t 551 TarFS::Directory::Inode() const 552 { 553 return fID; 554 } 555 556 557 // #pragma mark - 558 559 560 TarFS::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 570 TarFS::Symlink::~Symlink() 571 { 572 } 573 574 575 ssize_t 576 TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize) 577 { 578 return B_NOT_ALLOWED; 579 } 580 581 582 ssize_t 583 TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer, 584 size_t bufferSize) 585 { 586 return B_NOT_ALLOWED; 587 } 588 589 590 status_t 591 TarFS::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 598 int32 599 TarFS::Symlink::Type() const 600 { 601 return S_IFLNK; 602 } 603 604 605 off_t 606 TarFS::Symlink::Size() const 607 { 608 return fSize; 609 } 610 611 612 ino_t 613 TarFS::Symlink::Inode() const 614 { 615 return fID; 616 } 617 618 619 // #pragma mark - 620 621 622 TarFS::Volume::Volume() 623 : TarFS::Directory("Boot from CD-ROM") 624 { 625 } 626 627 628 TarFS::Volume::~Volume() 629 { 630 } 631 632 633 status_t 634 TarFS::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 734 status_t 735 TarFS::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 820 static status_t 821 tarfs_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 838 file_system_module_info gTarFileSystemModule = { 839 "file_systems/tarfs/v1", 840 kPartitionTypeTarFS, 841 NULL, // identify_file_system 842 tarfs_get_file_system 843 }; 844 845