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