1 /* 2 * Copyright 2009-2011, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <ctype.h> 8 #include <fcntl.h> 9 #include <errno.h> 10 #include <getopt.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <sys/stat.h> 15 #include <unistd.h> 16 17 #include <algorithm> 18 #include <new> 19 20 #include <fs_attr.h> 21 #include <String.h> 22 23 #include <AutoDeleter.h> 24 #include <HashString.h> 25 26 #include <util/OpenHashTable.h> 27 28 #include <package/hpkg/PackageContentHandler.h> 29 #include <package/hpkg/PackageDataReader.h> 30 #include <package/hpkg/PackageEntry.h> 31 #include <package/hpkg/PackageEntryAttribute.h> 32 #include <package/hpkg/PackageReader.h> 33 #include <package/BlockBufferCacheNoLock.h> 34 35 #include "package.h" 36 #include "StandardErrorOutput.h" 37 38 39 using namespace BPackageKit::BHPKG; 40 using BPackageKit::BBlockBufferCacheNoLock; 41 42 43 struct Entry { 44 Entry(Entry* parent, char* name, bool implicit) 45 : 46 fParent(parent), 47 fName(name), 48 fImplicit(implicit), 49 fSeen(false) 50 { 51 } 52 53 ~Entry() 54 { 55 _DeleteChildren(); 56 57 free(fName); 58 } 59 60 status_t Init() 61 { 62 return fChildren.Init(); 63 } 64 65 static status_t Create(Entry* parent, const char* name, bool implicit, 66 Entry*& _entry) 67 { 68 char* clonedName = strdup(name); 69 if (clonedName == NULL) 70 return B_NO_MEMORY; 71 72 Entry* entry = new(std::nothrow) Entry(parent, clonedName, implicit); 73 if (entry == NULL) { 74 free(clonedName); 75 return B_NO_MEMORY; 76 } 77 78 status_t error = entry->Init(); 79 if (error != B_OK) { 80 delete entry; 81 return error; 82 } 83 84 if (parent != NULL) 85 parent->fChildren.Insert(entry); 86 87 _entry = entry; 88 return B_OK; 89 } 90 91 Entry* Parent() const 92 { 93 return fParent; 94 } 95 96 const char* Name() const 97 { 98 return fName; 99 } 100 101 bool IsImplicit() const 102 { 103 return fImplicit; 104 } 105 106 void SetExplicit() 107 { 108 // remove all children and set this entry non-implicit 109 _DeleteChildren(); 110 fImplicit = false; 111 } 112 113 void SetSeen() 114 { 115 fSeen = true; 116 } 117 118 bool Seen() const 119 { 120 return fSeen; 121 } 122 123 Entry* FindChild(const char* name) const 124 { 125 return fChildren.Lookup(name); 126 } 127 128 private: 129 struct ChildHashDefinition { 130 typedef const char* KeyType; 131 typedef Entry ValueType; 132 133 size_t HashKey(const char* key) const 134 { 135 return string_hash(key); 136 } 137 138 size_t Hash(const Entry* value) const 139 { 140 return HashKey(value->Name()); 141 } 142 143 bool Compare(const char* key, const Entry* value) const 144 { 145 return strcmp(value->Name(), key) == 0; 146 } 147 148 Entry*& GetLink(Entry* value) const 149 { 150 return value->fHashTableNext; 151 } 152 }; 153 154 typedef BOpenHashTable<ChildHashDefinition> ChildTable; 155 156 private: 157 void _DeleteChildren() 158 { 159 Entry* child = fChildren.Clear(true); 160 while (child != NULL) { 161 Entry* next = child->fHashTableNext; 162 delete child; 163 child = next; 164 } 165 } 166 167 public: 168 Entry* fHashTableNext; 169 170 private: 171 Entry* fParent; 172 char* fName; 173 bool fImplicit; 174 bool fSeen; 175 ChildTable fChildren; 176 }; 177 178 179 struct PackageContentExtractHandler : BPackageContentHandler { 180 PackageContentExtractHandler(int packageFileFD) 181 : 182 fBufferCache(B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, 2), 183 fPackageFileReader(packageFileFD), 184 fDataBuffer(NULL), 185 fDataBufferSize(0), 186 fRootFilterEntry(NULL, NULL, true), 187 fBaseDirectory(AT_FDCWD), 188 fInfoFileName(NULL), 189 fErrorOccurred(false) 190 { 191 } 192 193 ~PackageContentExtractHandler() 194 { 195 free(fDataBuffer); 196 } 197 198 status_t Init() 199 { 200 status_t error = fBufferCache.Init(); 201 if (error != B_OK) 202 return error; 203 204 error = fRootFilterEntry.Init(); 205 if (error != B_OK) 206 return error; 207 208 fDataBufferSize = 64 * 1024; 209 fDataBuffer = malloc(fDataBufferSize); 210 if (fDataBuffer == NULL) 211 return B_NO_MEMORY; 212 213 return B_OK; 214 } 215 216 void SetBaseDirectory(int fd) 217 { 218 fBaseDirectory = fd; 219 } 220 221 void SetPackageInfoFile(const char* infoFileName) 222 { 223 fInfoFileName = infoFileName; 224 } 225 226 void SetExtractAll() 227 { 228 fRootFilterEntry.SetExplicit(); 229 } 230 231 status_t AddFilterEntry(const char* fileName) 232 { 233 // add all components of the path 234 Entry* entry = &fRootFilterEntry; 235 while (*fileName != 0) { 236 const char* nextSlash = strchr(fileName, '/'); 237 // no slash, just add the file name 238 if (nextSlash == NULL) { 239 return _AddFilterEntry(entry, fileName, strlen(fileName), 240 false, entry); 241 } 242 243 // find the start of the next component, skipping slashes 244 const char* nextComponent = nextSlash + 1; 245 while (*nextComponent == '/') 246 nextComponent++; 247 248 status_t error = _AddFilterEntry(entry, fileName, 249 nextSlash - fileName, *nextComponent != '\0', entry); 250 if (error != B_OK) 251 return error; 252 253 fileName = nextComponent; 254 } 255 256 return B_OK; 257 } 258 259 Entry* FindFilterEntry(const char* fileName) 260 { 261 // add all components of the path 262 Entry* entry = &fRootFilterEntry; 263 while (entry != NULL && *fileName != 0) { 264 const char* nextSlash = strchr(fileName, '/'); 265 // no slash, just add the file name 266 if (nextSlash == NULL) 267 return entry->FindChild(fileName); 268 269 // find the start of the next component, skipping slashes 270 const char* nextComponent = nextSlash + 1; 271 while (*nextComponent == '/') 272 nextComponent++; 273 274 BString componentName(fileName, nextSlash - fileName); 275 entry = entry->FindChild(componentName); 276 277 fileName = nextComponent; 278 } 279 280 return entry; 281 } 282 283 virtual status_t HandleEntry(BPackageEntry* entry) 284 { 285 // create a token 286 Token* token = new(std::nothrow) Token; 287 if (token == NULL) 288 return B_NO_MEMORY; 289 ObjectDeleter<Token> tokenDeleter(token); 290 291 // check whether this entry shall be ignored or is implicit 292 Entry* parentFilterEntry; 293 bool implicit; 294 if (entry->Parent() != NULL) { 295 Token* parentToken = (Token*)entry->Parent()->UserToken(); 296 if (parentToken == NULL) { 297 // parent is ignored, so ignore this entry, too 298 return B_OK; 299 } 300 301 parentFilterEntry = parentToken->filterEntry; 302 implicit = parentToken->implicit; 303 } else { 304 parentFilterEntry = &fRootFilterEntry; 305 implicit = fRootFilterEntry.IsImplicit(); 306 } 307 308 Entry* filterEntry = parentFilterEntry != NULL 309 ? parentFilterEntry->FindChild(entry->Name()) : NULL; 310 311 if (implicit && filterEntry == NULL) { 312 // parent is implicit and the filter doesn't include this entry 313 // -- ignore it 314 return B_OK; 315 } 316 317 // If the entry is in the filter, get its implicit flag. 318 if (filterEntry != NULL) { 319 implicit = filterEntry->IsImplicit(); 320 filterEntry->SetSeen(); 321 } 322 323 token->filterEntry = filterEntry; 324 token->implicit = implicit; 325 326 // get parent FD and the entry name 327 int parentFD; 328 const char* entryName; 329 _GetParentFDAndEntryName(entry, parentFD, entryName); 330 331 // check whether something is in the way 332 struct stat st; 333 bool entryExists = fstatat(parentFD, entryName, &st, 334 AT_SYMLINK_NOFOLLOW) == 0; 335 if (entryExists) { 336 if (S_ISREG(entry->Mode()) || S_ISLNK(entry->Mode())) { 337 // If the entry in the way is a regular file or a symlink, 338 // remove it, otherwise fail. 339 if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { 340 fprintf(stderr, "Error: Can't create entry \"%s\", since " 341 "something is in the way\n", 342 _EntryPath(entry).String()); 343 return B_FILE_EXISTS; 344 } 345 346 if (unlinkat(parentFD, entryName, 0) != 0) { 347 fprintf(stderr, "Error: Failed to unlink entry \"%s\": %s\n", 348 _EntryPath(entry).String(), strerror(errno)); 349 return errno; 350 } 351 352 entryExists = false; 353 } else if (S_ISDIR(entry->Mode())) { 354 // If the entry in the way is a directory, merge, otherwise 355 // fail. 356 if (!S_ISDIR(st.st_mode)) { 357 fprintf(stderr, "Error: Can't create directory \"%s\", " 358 "since something is in the way\n", 359 _EntryPath(entry).String()); 360 return B_FILE_EXISTS; 361 } 362 } 363 } 364 365 // create the entry 366 int fd = -1; 367 if (S_ISREG(entry->Mode())) { 368 if (implicit) { 369 fprintf(stderr, "Error: File \"%s\" was specified as a " 370 "path directory component.\n", _EntryPath(entry).String()); 371 return B_BAD_VALUE; 372 } 373 374 // create the file 375 fd = openat(parentFD, entryName, O_RDWR | O_CREAT | O_EXCL, 376 S_IRUSR | S_IWUSR); 377 // Note: We use read+write user permissions now -- so write 378 // operations (e.g. attributes) won't fail, but set them to the 379 // desired ones in HandleEntryDone(). 380 if (fd < 0) { 381 fprintf(stderr, "Error: Failed to create file \"%s\": %s\n", 382 _EntryPath(entry).String(), strerror(errno)); 383 return errno; 384 } 385 386 // write data 387 status_t error; 388 const BPackageData& data = entry->Data(); 389 if (data.IsEncodedInline()) { 390 BBufferDataReader dataReader(data.InlineData(), 391 data.CompressedSize()); 392 error = _ExtractFileData(&dataReader, data, fd); 393 } else 394 error = _ExtractFileData(&fPackageFileReader, data, fd); 395 396 if (error != B_OK) 397 return error; 398 } else if (S_ISLNK(entry->Mode())) { 399 if (implicit) { 400 fprintf(stderr, "Error: Symlink \"%s\" was specified as a " 401 "path directory component.\n", _EntryPath(entry).String()); 402 return B_BAD_VALUE; 403 } 404 405 // create the symlink 406 const char* symlinkPath = entry->SymlinkPath(); 407 if (symlinkat(symlinkPath != NULL ? symlinkPath : "", parentFD, 408 entryName) != 0) { 409 fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n", 410 _EntryPath(entry).String(), strerror(errno)); 411 return errno; 412 } 413 // TODO: Set symlink permissions? 414 } else if (S_ISDIR(entry->Mode())) { 415 // create the directory, if necessary 416 if (!entryExists 417 && mkdirat(parentFD, entryName, S_IRWXU) != 0) { 418 // Note: We use read+write+exec user permissions now -- so write 419 // operations (e.g. attributes) won't fail, but set them to the 420 // desired ones in HandleEntryDone(). 421 fprintf(stderr, "Error: Failed to create directory \"%s\": " 422 "%s\n", _EntryPath(entry).String(), strerror(errno)); 423 return errno; 424 } 425 } else { 426 fprintf(stderr, "Error: Invalid file type for entry \"%s\"\n", 427 _EntryPath(entry).String()); 428 return B_BAD_DATA; 429 } 430 431 // If not done yet (symlink, dir), open the node -- we need the FD. 432 if (fd < 0 && (!implicit || S_ISDIR(entry->Mode()))) { 433 fd = openat(parentFD, entryName, O_RDONLY | O_NOTRAVERSE); 434 if (fd < 0) { 435 fprintf(stderr, "Error: Failed to open entry \"%s\": %s\n", 436 _EntryPath(entry).String(), strerror(errno)); 437 return errno; 438 } 439 } 440 token->fd = fd; 441 442 // set the file times 443 if (!entryExists && !implicit) { 444 timespec times[2] = {entry->AccessTime(), entry->ModifiedTime()}; 445 futimens(fd, times); 446 447 // set user/group 448 // TODO:... 449 } 450 451 entry->SetUserToken(tokenDeleter.Detach()); 452 return B_OK; 453 } 454 455 virtual status_t HandleEntryAttribute(BPackageEntry* entry, 456 BPackageEntryAttribute* attribute) 457 { 458 // don't write attributes of ignored or implicit entries 459 Token* token = (Token*)entry->UserToken(); 460 if (token == NULL || token->implicit) 461 return B_OK; 462 463 int entryFD = token->fd; 464 465 // create the attribute 466 int fd = fs_fopen_attr(entryFD, attribute->Name(), attribute->Type(), 467 O_WRONLY | O_CREAT | O_TRUNC); 468 if (fd < 0) { 469 int parentFD; 470 const char* entryName; 471 _GetParentFDAndEntryName(entry, parentFD, entryName); 472 473 fprintf(stderr, "Error: Failed to create attribute \"%s\" of " 474 "file \"%s\": %s\n", attribute->Name(), 475 _EntryPath(entry).String(), strerror(errno)); 476 return errno; 477 } 478 479 // write data 480 status_t error; 481 const BPackageData& data = attribute->Data(); 482 if (data.IsEncodedInline()) { 483 BBufferDataReader dataReader(data.InlineData(), 484 data.CompressedSize()); 485 error = _ExtractFileData(&dataReader, data, fd); 486 } else 487 error = _ExtractFileData(&fPackageFileReader, data, fd); 488 489 fs_close_attr(fd); 490 491 return error; 492 } 493 494 virtual status_t HandleEntryDone(BPackageEntry* entry) 495 { 496 Token* token = (Token*)entry->UserToken(); 497 498 // set the node permissions for non-symlinks 499 if (token != NULL && !S_ISLNK(entry->Mode())) { 500 // get parent FD and entry name 501 int parentFD; 502 const char* entryName; 503 _GetParentFDAndEntryName(entry, parentFD, entryName); 504 505 if (fchmodat(parentFD, entryName, entry->Mode() & ALLPERMS, 506 /*AT_SYMLINK_NOFOLLOW*/0) != 0) { 507 fprintf(stderr, "Warning: Failed to set permissions of file " 508 "\"%s\": %s\n", _EntryPath(entry).String(), 509 strerror(errno)); 510 } 511 } 512 513 if (token != NULL) { 514 delete token; 515 entry->SetUserToken(NULL); 516 } 517 518 return B_OK; 519 } 520 521 virtual status_t HandlePackageAttribute( 522 const BPackageInfoAttributeValue& value) 523 { 524 return B_OK; 525 } 526 527 virtual void HandleErrorOccurred() 528 { 529 fErrorOccurred = true; 530 } 531 532 private: 533 struct Token { 534 Entry* filterEntry; 535 int fd; 536 bool implicit; 537 538 Token() 539 : 540 filterEntry(NULL), 541 fd(-1), 542 implicit(true) 543 { 544 } 545 546 ~Token() 547 { 548 if (fd >= 0) 549 close(fd); 550 } 551 }; 552 553 private: 554 status_t _AddFilterEntry(Entry* parentEntry, const char* _name, 555 size_t nameLength, bool implicit, Entry*& _entry) 556 { 557 BString name(_name, nameLength); 558 if (name.IsEmpty()) 559 return B_NO_MEMORY; 560 561 return Entry::Create(parentEntry, name.String(), implicit, _entry); 562 } 563 564 void _GetParentFDAndEntryName(BPackageEntry* entry, int& _parentFD, 565 const char*& _entryName) 566 { 567 _entryName = entry->Name(); 568 569 if (fInfoFileName != NULL 570 && strcmp(_entryName, B_HPKG_PACKAGE_INFO_FILE_NAME) == 0) { 571 _parentFD = AT_FDCWD; 572 _entryName = fInfoFileName; 573 } else { 574 _parentFD = entry->Parent() != NULL 575 ? ((Token*)entry->Parent()->UserToken())->fd : fBaseDirectory; 576 } 577 } 578 579 BString _EntryPath(const BPackageEntry* entry) 580 { 581 BString path; 582 583 if (const BPackageEntry* parent = entry->Parent()) { 584 path = _EntryPath(parent); 585 path << '/'; 586 } 587 588 path << entry->Name(); 589 return path; 590 } 591 592 status_t _ExtractFileData(BDataReader* dataReader, const BPackageData& data, 593 int fd) 594 { 595 // create a BPackageDataReader 596 BPackageDataReader* reader; 597 status_t error = BPackageDataReaderFactory(&fBufferCache) 598 .CreatePackageDataReader(dataReader, data, reader); 599 if (error != B_OK) 600 return error; 601 ObjectDeleter<BPackageDataReader> readerDeleter(reader); 602 603 // write the data 604 off_t bytesRemaining = data.UncompressedSize(); 605 off_t offset = 0; 606 while (bytesRemaining > 0) { 607 // read 608 size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining); 609 error = reader->ReadData(offset, fDataBuffer, toCopy); 610 if (error != B_OK) { 611 fprintf(stderr, "Error: Failed to read data: %s\n", 612 strerror(error)); 613 return error; 614 } 615 616 // write 617 ssize_t bytesWritten = write_pos(fd, offset, fDataBuffer, toCopy); 618 if (bytesWritten < 0) { 619 fprintf(stderr, "Error: Failed to write data: %s\n", 620 strerror(errno)); 621 return errno; 622 } 623 if ((size_t)bytesWritten != toCopy) { 624 fprintf(stderr, "Error: Failed to write all data (%zd of " 625 "%zu)\n", bytesWritten, toCopy); 626 return B_ERROR; 627 } 628 629 offset += toCopy; 630 bytesRemaining -= toCopy; 631 } 632 633 return B_OK; 634 } 635 636 private: 637 BBlockBufferCacheNoLock fBufferCache; 638 BFDDataReader fPackageFileReader; 639 void* fDataBuffer; 640 size_t fDataBufferSize; 641 Entry fRootFilterEntry; 642 int fBaseDirectory; 643 const char* fInfoFileName; 644 bool fErrorOccurred; 645 }; 646 647 648 int 649 command_extract(int argc, const char* const* argv) 650 { 651 const char* changeToDirectory = NULL; 652 const char* packageInfoFileName = NULL; 653 654 while (true) { 655 static struct option sLongOptions[] = { 656 { "help", no_argument, 0, 'h' }, 657 { 0, 0, 0, 0 } 658 }; 659 660 opterr = 0; // don't print errors 661 int c = getopt_long(argc, (char**)argv, "+C:hi:", sLongOptions, NULL); 662 if (c == -1) 663 break; 664 665 switch (c) { 666 case 'C': 667 changeToDirectory = optarg; 668 break; 669 670 case 'h': 671 print_usage_and_exit(false); 672 break; 673 674 case 'i': 675 packageInfoFileName = optarg; 676 break; 677 678 default: 679 print_usage_and_exit(true); 680 break; 681 } 682 } 683 684 // At least one argument should remain -- the package file name. 685 if (optind + 1 > argc) 686 print_usage_and_exit(true); 687 688 const char* packageFileName = argv[optind++]; 689 690 // open package 691 StandardErrorOutput errorOutput; 692 BPackageReader packageReader(&errorOutput); 693 status_t error = packageReader.Init(packageFileName); 694 if (error != B_OK) 695 return 1; 696 697 PackageContentExtractHandler handler(packageReader.PackageFileFD()); 698 error = handler.Init(); 699 if (error != B_OK) 700 return 1; 701 702 // If entries to extract have been specified explicitly, add those to the 703 // filtered ones. 704 int explicitEntriesIndex = optind; 705 if (optind < argc) { 706 while (optind < argc) { 707 const char* entryName = argv[optind++]; 708 if (entryName[0] == '\0' || entryName[0] == '/') { 709 fprintf(stderr, "Error: Invalid entry name: \"%s\".", 710 entryName); 711 return 1; 712 } 713 714 if (handler.AddFilterEntry(entryName) != B_OK) 715 return 1; 716 } 717 } else 718 handler.SetExtractAll(); 719 720 // get the target directory, if requested 721 if (changeToDirectory != NULL) { 722 int currentDirFD = open(changeToDirectory, O_RDONLY); 723 if (currentDirFD < 0) { 724 fprintf(stderr, "Error: Failed to change the current working " 725 "directory to \"%s\": %s\n", changeToDirectory, 726 strerror(errno)); 727 return 1; 728 } 729 730 handler.SetBaseDirectory(currentDirFD); 731 } 732 733 // If a package info file name is given, set it. 734 if (packageInfoFileName != NULL) 735 handler.SetPackageInfoFile(packageInfoFileName); 736 737 // extract 738 error = packageReader.ParseContent(&handler); 739 if (error != B_OK) 740 return 1; 741 742 // check whether all explicitly specified entries have been extracted 743 if (explicitEntriesIndex < argc) { 744 for (int i = explicitEntriesIndex; i < argc; i++) { 745 if (Entry* entry = handler.FindFilterEntry(argv[i])) { 746 if (!entry->Seen()) { 747 fprintf(stderr, "Warning: Entry \"%s\" not found.\n", 748 argv[i]); 749 } 750 } 751 } 752 } 753 754 return 0; 755 } 756