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