1 /* 2 * Copyright 2005, Ingo Weinhold, bonefish@cs.tu-berlin.de. All rights reserved. 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 kCompressedArchiveOffset = 192 * 1024; // at 192 kB 37 static const size_t kTarRegionSize = 4 * 1024 * 1024; // 4 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 class Volume : public TarFS::Directory { 130 public: 131 Volume(); 132 ~Volume(); 133 134 status_t Init(boot::Partition *partition); 135 136 TarFS::Directory *Root() { return this; } 137 }; 138 139 } // namespace TarFS 140 141 142 static int32 sNextID = 1; 143 144 145 // #pragma mark - 146 147 148 bool 149 skip_gzip_header(z_stream *stream) 150 { 151 uint8 *buffer = (uint8 *)stream->next_in; 152 153 // check magic and skip method 154 if (buffer[0] != 0x1f || buffer[1] != 0x8b) 155 return false; 156 157 uint32 offset = 3; 158 159 // we need the flags field to determine the length of the header 160 int flags = buffer[offset++]; 161 162 offset += 6; 163 164 if ((flags & 0x04) != 0) { 165 // skip extra field 166 offset += buffer[offset++] | (buffer[offset++] << 8); 167 if (offset >= stream->avail_in) 168 return false; 169 } 170 if ((flags & 0x08) != 0) { 171 // skip original name 172 while (buffer[offset++]) 173 ; 174 } 175 if ((flags & 0x10) != 0) { 176 // skip comment 177 while (buffer[offset++]) 178 ; 179 } 180 if ((flags & 0x02) != 0) { 181 // skip CRC 182 offset += 2; 183 } 184 185 if (offset >= stream->avail_in) 186 return false; 187 188 stream->next_in += offset; 189 stream->avail_in -= offset; 190 return true; 191 } 192 193 194 // #pragma mark - 195 196 197 TarFS::Entry::Entry(const char *name) 198 : 199 fName(name), 200 fID(sNextID++) 201 { 202 } 203 204 205 // #pragma mark - 206 207 208 TarFS::File::File(tar_header *header, const char *name) 209 : TarFS::Entry(name), 210 fHeader(header) 211 { 212 fSize = strtol(header->size, NULL, 8); 213 } 214 215 216 TarFS::File::~File() 217 { 218 } 219 220 221 ssize_t 222 TarFS::File::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize) 223 { 224 TRACE(("tarfs: read at %Ld, %lu bytes, fSize = %Ld\n", pos, bufferSize, fSize)); 225 226 if (pos < 0 || !buffer) 227 return B_BAD_VALUE; 228 229 if (pos >= fSize || bufferSize == 0) 230 return 0; 231 232 size_t toRead = fSize - pos; 233 if (toRead > bufferSize) 234 toRead = bufferSize; 235 236 memcpy(buffer, (char*)fHeader + BLOCK_SIZE + pos, toRead); 237 238 return toRead; 239 } 240 241 242 ssize_t 243 TarFS::File::WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize) 244 { 245 return B_NOT_ALLOWED; 246 } 247 248 249 status_t 250 TarFS::File::GetName(char *nameBuffer, size_t bufferSize) const 251 { 252 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize ? B_BUFFER_OVERFLOW : B_OK; 253 } 254 255 256 int32 257 TarFS::File::Type() const 258 { 259 return S_IFREG; 260 } 261 262 263 off_t 264 TarFS::File::Size() const 265 { 266 return fSize; 267 } 268 269 270 ino_t 271 TarFS::File::Inode() const 272 { 273 return fID; 274 } 275 276 277 // #pragma mark - 278 279 TarFS::Directory::Directory(const char *name) 280 : TarFS::Entry(name) 281 { 282 } 283 284 285 TarFS::Directory::~Directory() 286 { 287 while (TarFS::Entry *entry = fEntries.Head()) { 288 fEntries.Remove(entry); 289 delete entry; 290 } 291 } 292 293 294 status_t 295 TarFS::Directory::Open(void **_cookie, int mode) 296 { 297 _inherited::Open(_cookie, mode); 298 299 EntryIterator *iterator = new EntryIterator(fEntries.GetIterator()); 300 if (iterator == NULL) 301 return B_NO_MEMORY; 302 303 *_cookie = iterator; 304 305 return B_OK; 306 } 307 308 309 status_t 310 TarFS::Directory::Close(void *cookie) 311 { 312 _inherited::Close(cookie); 313 314 delete (EntryIterator *)cookie; 315 return B_OK; 316 } 317 318 319 status_t 320 TarFS::Directory::GetName(char *nameBuffer, size_t bufferSize) const 321 { 322 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize ? B_BUFFER_OVERFLOW : B_OK; 323 } 324 325 326 TarFS::Entry * 327 TarFS::Directory::LookupEntry(const char *name) 328 { 329 EntryIterator iterator(fEntries.GetIterator()); 330 331 while (iterator.HasNext()) { 332 TarFS::Entry *entry = iterator.Next(); 333 if (strcmp(name, entry->Name()) == 0) 334 return entry; 335 } 336 337 return NULL; 338 } 339 340 341 ::Node * 342 TarFS::Directory::Lookup(const char *name, bool /*traverseLinks*/) 343 { 344 if (TarFS::Entry *entry = LookupEntry(name)) { 345 entry->ToNode()->Acquire(); 346 // our entries are not supposed to be deleted after use 347 return entry->ToNode(); 348 } 349 350 return NULL; 351 } 352 353 354 status_t 355 TarFS::Directory::GetNextEntry(void *_cookie, char *name, size_t size) 356 { 357 EntryIterator *iterator = (EntryIterator *)_cookie; 358 TarFS::Entry *entry = iterator->Next(); 359 360 if (entry != NULL) { 361 strlcpy(name, entry->Name(), size); 362 return B_OK; 363 } 364 365 return B_ENTRY_NOT_FOUND; 366 } 367 368 369 status_t 370 TarFS::Directory::GetNextNode(void *_cookie, Node **_node) 371 { 372 EntryIterator *iterator = (EntryIterator *)_cookie; 373 TarFS::Entry *entry = iterator->Next(); 374 375 if (entry != NULL) { 376 *_node = entry->ToNode(); 377 return B_OK; 378 } 379 return B_ENTRY_NOT_FOUND; 380 } 381 382 383 status_t 384 TarFS::Directory::Rewind(void *_cookie) 385 { 386 EntryIterator *iterator = (EntryIterator *)_cookie; 387 *iterator = fEntries.GetIterator(); 388 return B_OK; 389 } 390 391 392 status_t 393 TarFS::Directory::AddDirectory(char *dirName, TarFS::Directory **_dir) 394 { 395 char *subDir = strchr(dirName, '/'); 396 if (subDir) { 397 // skip slashes 398 while (*subDir == '/') { 399 *subDir = '\0'; 400 subDir++; 401 } 402 403 if (*subDir == '\0') { 404 // a trailing slash 405 subDir = NULL; 406 } 407 } 408 409 // check, whether the directory does already exist 410 Entry *entry = LookupEntry(dirName); 411 TarFS::Directory *dir = (entry ? entry->ToTarDirectory() : NULL); 412 if (entry) { 413 if (!dir) 414 return B_ERROR; 415 } else { 416 // doesn't exist yet -- create it 417 dir = new TarFS::Directory(dirName); 418 if (!dir) 419 return B_NO_MEMORY; 420 421 fEntries.Add(dir); 422 } 423 424 // recursively create the subdirectories 425 if (subDir) { 426 status_t error = dir->AddDirectory(subDir, &dir); 427 if (error != B_OK) 428 return error; 429 } 430 431 if (_dir) 432 *_dir = dir; 433 434 return B_OK; 435 } 436 437 438 status_t 439 TarFS::Directory::AddFile(tar_header *header) 440 { 441 char *leaf = strrchr(header->name, '/'); 442 char *dirName = NULL; 443 if (leaf) { 444 dirName = header->name; 445 *leaf = '\0'; 446 leaf++; 447 } else 448 leaf = header->name; 449 450 // create the parent directory 451 TarFS::Directory *dir = this; 452 if (dirName) { 453 status_t error = AddDirectory(dirName, &dir); 454 if (error != B_OK) 455 return error; 456 } 457 458 // create the file 459 TarFS::File *file = new TarFS::File(header, leaf); 460 if (!file) 461 return B_NO_MEMORY; 462 463 dir->fEntries.Add(file); 464 465 return B_OK; 466 } 467 468 469 bool 470 TarFS::Directory::IsEmpty() 471 { 472 return fEntries.IsEmpty(); 473 } 474 475 476 ino_t 477 TarFS::Directory::Inode() const 478 { 479 return fID; 480 } 481 482 483 // #pragma mark - 484 485 486 TarFS::Volume::Volume() 487 : TarFS::Directory("Boot from CD-ROM") 488 { 489 } 490 491 492 TarFS::Volume::~Volume() 493 { 494 } 495 496 497 status_t 498 TarFS::Volume::Init(boot::Partition *partition) 499 { 500 void *cookie; 501 status_t error = partition->Open(&cookie, O_RDONLY); 502 if (error != B_OK) 503 return error; 504 505 struct PartitionCloser { 506 boot::Partition *partition; 507 void *cookie; 508 509 PartitionCloser(boot::Partition *partition, void *cookie) 510 : partition(partition), 511 cookie(cookie) 512 { 513 } 514 515 ~PartitionCloser() 516 { 517 partition->Close(cookie); 518 } 519 } _(partition, cookie); 520 521 RegionDeleter regionDeleter; 522 523 char *out = NULL; 524 525 char in[2048]; 526 z_stream zStream = { 527 (Bytef*)in, // next in 528 sizeof(in), // avail in 529 0, // total in 530 NULL, // next out 531 0, // avail out 532 0, // total out 533 0, // msg 534 0, // state 535 Z_NULL, // zalloc 536 Z_NULL, // zfree 537 Z_NULL, // opaque 538 0, // data type 539 0, // adler 540 0, // reserved 541 }; 542 543 int status; 544 uint32 offset = kCompressedArchiveOffset; 545 546 do { 547 if (partition->ReadAt(cookie, offset, in, sizeof(in)) != sizeof(in)) { 548 status = Z_STREAM_ERROR; 549 break; 550 } 551 552 zStream.avail_in = sizeof(in); 553 zStream.next_in = (Bytef *)in; 554 555 if (offset == kCompressedArchiveOffset) { 556 // check and skip gzip header 557 if (!skip_gzip_header(&zStream)) 558 return B_BAD_DATA; 559 560 if (platform_allocate_region((void **)&out, kTarRegionSize, 561 B_READ_AREA | B_WRITE_AREA) != B_OK) { 562 TRACE(("tarfs: allocating region failed!\n")); 563 return B_NO_MEMORY; 564 } 565 566 regionDeleter.SetTo(out); 567 zStream.avail_out = kTarRegionSize; 568 zStream.next_out = (Bytef *)out; 569 570 status = inflateInit2(&zStream, -15); 571 if (status != Z_OK) 572 return B_ERROR; 573 } 574 575 status = inflate(&zStream, Z_SYNC_FLUSH); 576 offset += sizeof(in); 577 578 if (zStream.avail_in != 0 && status != Z_STREAM_END) 579 dprintf("tarfs: didn't read whole block!\n"); 580 } while (status == Z_OK); 581 582 inflateEnd(&zStream); 583 584 if (status != Z_STREAM_END) { 585 TRACE(("tarfs: inflating failed: %d!\n", status)); 586 return B_BAD_DATA; 587 } 588 589 status = B_OK; 590 591 // parse the tar file 592 char *block = out; 593 int blockCount = zStream.total_out / BLOCK_SIZE; 594 int blockIndex = 0; 595 596 while (blockIndex < blockCount) { 597 // check header 598 tar_header *header = (tar_header*)(block + blockIndex * BLOCK_SIZE); 599 //dump_header(*header); 600 601 if (header->magic[0] == '\0') 602 break; 603 604 if (strcmp(header->magic, kTarHeaderMagic) != 0) { 605 if (strcmp(header->magic, kOldTarHeaderMagic) != 0) { 606 dprintf("Bad tar header magic in block %d.\n", blockIndex); 607 status = B_BAD_DATA; 608 break; 609 } 610 } 611 612 off_t size = strtol(header->size, NULL, 8); 613 614 TRACE(("tarfs: \"%s\", %Ld bytes\n", header->name, size)); 615 616 // TODO: this is old-style GNU tar which probably won't work with newer ones... 617 switch (header->type) { 618 case TAR_FILE: 619 case TAR_FILE2: 620 status = AddFile(header); 621 break; 622 623 case TAR_DIRECTORY: 624 status = AddDirectory(header->name, NULL); 625 break; 626 627 case TAR_LONG_NAME: 628 // this is a long file name 629 // TODO: read long name 630 default: 631 // unsupported type 632 status = B_ERROR; 633 break; 634 } 635 636 if (status != B_OK) 637 return status; 638 639 // next block 640 blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE; 641 } 642 643 if (status != B_OK) 644 return status; 645 646 regionDeleter.Detach(); 647 return B_OK; 648 } 649 650 651 // #pragma mark - 652 653 654 static status_t 655 tarfs_get_file_system(boot::Partition *partition, ::Directory **_root) 656 { 657 TarFS::Volume *volume = new TarFS::Volume; 658 if (volume == NULL) 659 return B_NO_MEMORY; 660 661 if (volume->Init(partition) < B_OK) { 662 TRACE(("Initializing tarfs failed\n")); 663 delete volume; 664 return B_ERROR; 665 } 666 667 *_root = volume->Root(); 668 return B_OK; 669 } 670 671 672 file_system_module_info gTarFileSystemModule = { 673 "file_systems/tarfs/v1", 674 kPartitionTypeTarFS, 675 tarfs_get_file_system 676 }; 677 678