1 /* 2 * Copyright 2013-2014, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Ingo Weinhold <ingo_weinhold@gmx.de> 7 */ 8 9 10 #include "CommitTransactionHandler.h" 11 12 #include <errno.h> 13 #include <grp.h> 14 #include <pwd.h> 15 16 #include <File.h> 17 #include <Path.h> 18 19 #include <AutoDeleter.h> 20 #include <CopyEngine.h> 21 #include <NotOwningEntryRef.h> 22 #include <package/CommitTransactionResult.h> 23 #include <package/DaemonDefs.h> 24 #include <RemoveEngine.h> 25 26 #include "Constants.h" 27 #include "DebugSupport.h" 28 #include "Exception.h" 29 #include "PackageFileManager.h" 30 #include "VolumeState.h" 31 32 33 using namespace BPackageKit::BPrivate; 34 35 using BPackageKit::BTransactionIssue; 36 37 38 // #pragma mark - TransactionIssueBuilder 39 40 41 struct CommitTransactionHandler::TransactionIssueBuilder { 42 TransactionIssueBuilder(BTransactionIssue::BType type, 43 Package* package = NULL) 44 : 45 fType(type), 46 fPackageName(package != NULL ? package->FileName() : BString()), 47 fPath1(), 48 fPath2(), 49 fSystemError(B_OK), 50 fExitCode(0) 51 { 52 } 53 54 TransactionIssueBuilder& SetPath1(const BString& path) 55 { 56 fPath1 = path; 57 return *this; 58 } 59 60 TransactionIssueBuilder& SetPath1(const FSUtils::Entry& entry) 61 { 62 return SetPath1(entry.Path()); 63 } 64 65 TransactionIssueBuilder& SetPath2(const BString& path) 66 { 67 fPath2 = path; 68 return *this; 69 } 70 71 TransactionIssueBuilder& SetPath2(const FSUtils::Entry& entry) 72 { 73 return SetPath2(entry.Path()); 74 } 75 76 TransactionIssueBuilder& SetSystemError(status_t error) 77 { 78 fSystemError = error; 79 return *this; 80 } 81 82 TransactionIssueBuilder& SetExitCode(int exitCode) 83 { 84 fExitCode = exitCode; 85 return *this; 86 } 87 88 BTransactionIssue BuildIssue(Package* package) const 89 { 90 BString packageName(fPackageName); 91 if (packageName.IsEmpty() && package != NULL) 92 packageName = package->FileName(); 93 94 return BTransactionIssue(fType, packageName, fPath1, fPath2, 95 fSystemError, fExitCode); 96 } 97 98 private: 99 BTransactionIssue::BType fType; 100 BString fPackageName; 101 BString fPath1; 102 BString fPath2; 103 status_t fSystemError; 104 int fExitCode; 105 }; 106 107 108 // #pragma mark - CommitTransactionHandler 109 110 111 CommitTransactionHandler::CommitTransactionHandler(Volume* volume, 112 PackageFileManager* packageFileManager, BCommitTransactionResult& result) 113 : 114 fVolume(volume), 115 fPackageFileManager(packageFileManager), 116 fVolumeState(NULL), 117 fVolumeStateIsActive(false), 118 fPackagesToActivate(), 119 fPackagesToDeactivate(), 120 fAddedPackages(), 121 fRemovedPackages(), 122 fPackagesAlreadyAdded(), 123 fPackagesAlreadyRemoved(), 124 fOldStateDirectory(), 125 fOldStateDirectoryRef(), 126 fOldStateDirectoryName(), 127 fTransactionDirectoryRef(), 128 fWritableFilesDirectory(), 129 fAddedGroups(), 130 fAddedUsers(), 131 fFSTransaction(), 132 fResult(result), 133 fCurrentPackage(NULL) 134 { 135 } 136 137 138 CommitTransactionHandler::~CommitTransactionHandler() 139 { 140 // Delete Package objects we created in case of error (on success 141 // fPackagesToActivate will be empty). 142 int32 count = fPackagesToActivate.CountItems(); 143 for (int32 i = 0; i < count; i++) { 144 Package* package = fPackagesToActivate.ItemAt(i); 145 if (fPackagesAlreadyAdded.find(package) 146 == fPackagesAlreadyAdded.end()) { 147 delete package; 148 } 149 } 150 151 delete fVolumeState; 152 } 153 154 155 void 156 CommitTransactionHandler::Init(VolumeState* volumeState, 157 bool isActiveVolumeState, const PackageSet& packagesAlreadyAdded, 158 const PackageSet& packagesAlreadyRemoved) 159 { 160 fVolumeState = volumeState->Clone(); 161 if (fVolumeState == NULL) 162 throw std::bad_alloc(); 163 164 fVolumeStateIsActive = isActiveVolumeState; 165 166 for (PackageSet::const_iterator it = packagesAlreadyAdded.begin(); 167 it != packagesAlreadyAdded.end(); ++it) { 168 Package* package = fVolumeState->FindPackage((*it)->FileName()); 169 fPackagesAlreadyAdded.insert(package); 170 } 171 172 for (PackageSet::const_iterator it = packagesAlreadyRemoved.begin(); 173 it != packagesAlreadyRemoved.end(); ++it) { 174 Package* package = fVolumeState->FindPackage((*it)->FileName()); 175 fPackagesAlreadyRemoved.insert(package); 176 } 177 } 178 179 180 void 181 CommitTransactionHandler::HandleRequest(BMessage* request) 182 { 183 status_t error; 184 BActivationTransaction transaction(request, &error); 185 if (error == B_OK) 186 error = transaction.InitCheck(); 187 if (error != B_OK) { 188 if (error == B_NO_MEMORY) 189 throw Exception(B_TRANSACTION_NO_MEMORY); 190 throw Exception(B_TRANSACTION_BAD_REQUEST); 191 } 192 193 HandleRequest(transaction); 194 } 195 196 197 void 198 CommitTransactionHandler::HandleRequest( 199 const BActivationTransaction& transaction) 200 { 201 // check the change count 202 if (transaction.ChangeCount() != fVolume->ChangeCount()) 203 throw Exception(B_TRANSACTION_CHANGE_COUNT_MISMATCH); 204 205 // collect the packages to deactivate 206 _GetPackagesToDeactivate(transaction); 207 208 // read the packages to activate 209 _ReadPackagesToActivate(transaction); 210 211 // anything to do at all? 212 if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) { 213 WARN("Bad package activation request: no packages to activate or" 214 " deactivate\n"); 215 throw Exception(B_TRANSACTION_BAD_REQUEST); 216 } 217 218 _ApplyChanges(); 219 } 220 221 222 void 223 CommitTransactionHandler::HandleRequest() 224 { 225 for (PackageSet::const_iterator it = fPackagesAlreadyAdded.begin(); 226 it != fPackagesAlreadyAdded.end(); ++it) { 227 if (!fPackagesToActivate.AddItem(*it)) 228 throw std::bad_alloc(); 229 } 230 231 fPackagesToDeactivate = fPackagesAlreadyRemoved; 232 233 _ApplyChanges(); 234 } 235 236 237 void 238 CommitTransactionHandler::Revert() 239 { 240 // move packages to activate back to transaction directory 241 _RevertAddPackagesToActivate(); 242 243 // move packages to deactivate back to packages directory 244 _RevertRemovePackagesToDeactivate(); 245 246 // revert user and group changes 247 _RevertUserGroupChanges(); 248 249 // Revert all other FS operations, i.e. the writable files changes as 250 // well as the creation of the old state directory. 251 fFSTransaction.RollBack(); 252 } 253 254 255 VolumeState* 256 CommitTransactionHandler::DetachVolumeState() 257 { 258 VolumeState* result = fVolumeState; 259 fVolumeState = NULL; 260 return result; 261 } 262 263 264 void 265 CommitTransactionHandler::_GetPackagesToDeactivate( 266 const BActivationTransaction& transaction) 267 { 268 // get the number of packages to deactivate 269 const BStringList& packagesToDeactivate 270 = transaction.PackagesToDeactivate(); 271 int32 packagesToDeactivateCount = packagesToDeactivate.CountStrings(); 272 if (packagesToDeactivateCount == 0) 273 return; 274 275 for (int32 i = 0; i < packagesToDeactivateCount; i++) { 276 BString packageName = packagesToDeactivate.StringAt(i); 277 Package* package = fVolumeState->FindPackage(packageName); 278 if (package == NULL) { 279 throw Exception(B_TRANSACTION_NO_SUCH_PACKAGE) 280 .SetPackageName(packageName); 281 } 282 283 fPackagesToDeactivate.insert(package); 284 } 285 } 286 287 288 void 289 CommitTransactionHandler::_ReadPackagesToActivate( 290 const BActivationTransaction& transaction) 291 { 292 // get the number of packages to activate 293 const BStringList& packagesToActivate 294 = transaction.PackagesToActivate(); 295 int32 packagesToActivateCount = packagesToActivate.CountStrings(); 296 if (packagesToActivateCount == 0) 297 return; 298 299 // check the transaction directory name -- we only allow a simple 300 // subdirectory of the admin directory 301 const BString& transactionDirectoryName 302 = transaction.TransactionDirectoryName(); 303 if (transactionDirectoryName.IsEmpty() 304 || transactionDirectoryName.FindFirst('/') >= 0 305 || transactionDirectoryName == "." 306 || transactionDirectoryName == "..") { 307 WARN("Bad package activation request: malformed transaction" 308 " directory name: \"%s\"\n", transactionDirectoryName.String()); 309 throw Exception(B_TRANSACTION_BAD_REQUEST); 310 } 311 312 // open the directory 313 RelativePath directoryPath(kAdminDirectoryName, 314 transactionDirectoryName); 315 BDirectory directory; 316 status_t error = _OpenPackagesSubDirectory(directoryPath, false, directory); 317 if (error == B_OK) { 318 error = directory.GetNodeRef(&fTransactionDirectoryRef); 319 if (error != B_OK) { 320 ERROR("Failed to get transaction directory node ref: %s\n", 321 strerror(error)); 322 } 323 } else 324 ERROR("Failed to open transaction directory: %s\n", strerror(error)); 325 326 if (error != B_OK) { 327 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 328 .SetPath1(_GetPath( 329 FSUtils::Entry(fVolume->PackagesDirectoryRef(), 330 directoryPath.ToString()), 331 directoryPath.ToString())) 332 .SetSystemError(error); 333 } 334 335 // read the packages 336 for (int32 i = 0; i < packagesToActivateCount; i++) { 337 BString packageName = packagesToActivate.StringAt(i); 338 339 // make sure it doesn't clash with an already existing package 340 Package* package = fVolumeState->FindPackage(packageName); 341 if (package != NULL) { 342 if (fPackagesAlreadyAdded.find(package) 343 != fPackagesAlreadyAdded.end()) { 344 if (!fPackagesToActivate.AddItem(package)) 345 throw Exception(B_TRANSACTION_NO_MEMORY); 346 continue; 347 } 348 349 if (fPackagesToDeactivate.find(package) 350 == fPackagesToDeactivate.end()) { 351 throw Exception(B_TRANSACTION_PACKAGE_ALREADY_EXISTS) 352 .SetPackageName(packageName); 353 } 354 } 355 356 // read the package 357 error = fPackageFileManager->CreatePackage( 358 NotOwningEntryRef(fTransactionDirectoryRef, packageName), 359 package); 360 if (error != B_OK) { 361 if (error == B_NO_MEMORY) 362 throw Exception(B_TRANSACTION_NO_MEMORY); 363 throw Exception(B_TRANSACTION_FAILED_TO_READ_PACKAGE_FILE) 364 .SetPackageName(packageName) 365 .SetPath1(_GetPath( 366 FSUtils::Entry( 367 NotOwningEntryRef(fTransactionDirectoryRef, 368 packageName)), 369 packageName)) 370 .SetSystemError(error); 371 } 372 373 if (!fPackagesToActivate.AddItem(package)) { 374 delete package; 375 throw Exception(B_TRANSACTION_NO_MEMORY); 376 } 377 } 378 } 379 380 381 void 382 CommitTransactionHandler::_ApplyChanges() 383 { 384 // create an old state directory 385 _CreateOldStateDirectory(); 386 387 // move packages to deactivate to old state directory 388 _RemovePackagesToDeactivate(); 389 390 // move packages to activate to packages directory 391 _AddPackagesToActivate(); 392 393 // activate/deactivate packages 394 _ChangePackageActivation(fAddedPackages, fRemovedPackages); 395 396 if (fVolumeStateIsActive) { 397 // run post-installation scripts 398 _RunPostInstallScripts(); 399 } else { 400 // TODO: Make sure the post-install scripts are run on next boot! 401 } 402 403 // removed packages have been deleted, new packages shall not be deleted 404 fAddedPackages.clear(); 405 fRemovedPackages.clear(); 406 fPackagesToActivate.MakeEmpty(false); 407 fPackagesToDeactivate.clear(); 408 } 409 410 411 void 412 CommitTransactionHandler::_CreateOldStateDirectory() 413 { 414 // construct a nice name from the current date and time 415 time_t nowSeconds = time(NULL); 416 struct tm now; 417 BString baseName; 418 if (localtime_r(&nowSeconds, &now) != NULL) { 419 baseName.SetToFormat("state_%d-%02d-%02d_%02d:%02d:%02d", 420 1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour, 421 now.tm_min, now.tm_sec); 422 } else 423 baseName = "state"; 424 425 if (baseName.IsEmpty()) 426 throw Exception(B_TRANSACTION_NO_MEMORY); 427 428 // make sure the directory doesn't exist yet 429 BDirectory adminDirectory; 430 status_t error = _OpenPackagesSubDirectory( 431 RelativePath(kAdminDirectoryName), true, adminDirectory); 432 if (error != B_OK) { 433 ERROR("Failed to open administrative directory: %s\n", strerror(error)); 434 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 435 .SetPath1(_GetPath( 436 FSUtils::Entry(fVolume->PackagesDirectoryRef(), 437 kAdminDirectoryName), 438 kAdminDirectoryName)) 439 .SetSystemError(error); 440 } 441 442 int uniqueId = 1; 443 BString directoryName = baseName; 444 while (BEntry(&adminDirectory, directoryName).Exists()) { 445 directoryName.SetToFormat("%s-%d", baseName.String(), uniqueId++); 446 if (directoryName.IsEmpty()) 447 throw Exception(B_TRANSACTION_NO_MEMORY); 448 } 449 450 // create the directory 451 FSTransaction::CreateOperation createOldStateDirectoryOperation( 452 &fFSTransaction, FSUtils::Entry(adminDirectory, directoryName)); 453 454 error = adminDirectory.CreateDirectory(directoryName, 455 &fOldStateDirectory); 456 if (error == B_OK) { 457 createOldStateDirectoryOperation.Finished(); 458 459 fOldStateDirectoryName = directoryName; 460 461 error = fOldStateDirectory.GetNodeRef(&fOldStateDirectoryRef); 462 if (error != B_OK) 463 ERROR("Failed get old state directory ref: %s\n", strerror(error)); 464 } else 465 ERROR("Failed to create old state directory: %s\n", strerror(error)); 466 467 if (error != B_OK) { 468 throw Exception(B_TRANSACTION_FAILED_TO_CREATE_DIRECTORY) 469 .SetPath1(_GetPath( 470 FSUtils::Entry(adminDirectory, directoryName), 471 directoryName)) 472 .SetSystemError(error); 473 } 474 475 // write the old activation file 476 BEntry activationFile; 477 _WriteActivationFile(RelativePath(kAdminDirectoryName, directoryName), 478 kActivationFileName, PackageSet(), PackageSet(), activationFile); 479 480 fResult.SetOldStateDirectory(fOldStateDirectoryName); 481 } 482 483 484 void 485 CommitTransactionHandler::_RemovePackagesToDeactivate() 486 { 487 if (fPackagesToDeactivate.empty()) 488 return; 489 490 for (PackageSet::const_iterator it = fPackagesToDeactivate.begin(); 491 it != fPackagesToDeactivate.end(); ++it) { 492 Package* package = *it; 493 494 // When deactivating (or updating) a system package, don't do that live. 495 if (_IsSystemPackage(package)) 496 fVolumeStateIsActive = false; 497 498 if (fPackagesAlreadyRemoved.find(package) 499 != fPackagesAlreadyRemoved.end()) { 500 fRemovedPackages.insert(package); 501 continue; 502 } 503 504 // get a BEntry for the package 505 NotOwningEntryRef entryRef(package->EntryRef()); 506 507 BEntry entry; 508 status_t error = entry.SetTo(&entryRef); 509 if (error != B_OK) { 510 ERROR("Failed to get package entry for %s: %s\n", 511 package->FileName().String(), strerror(error)); 512 throw Exception(B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH) 513 .SetPath1(package->FileName()) 514 .SetPackageName(package->FileName()) 515 .SetSystemError(error); 516 } 517 518 // move entry 519 fRemovedPackages.insert(package); 520 521 error = entry.MoveTo(&fOldStateDirectory); 522 if (error != B_OK) { 523 fRemovedPackages.erase(package); 524 ERROR("Failed to move old package %s from packages directory: %s\n", 525 package->FileName().String(), strerror(error)); 526 throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) 527 .SetPath1( 528 _GetPath(FSUtils::Entry(entryRef), package->FileName())) 529 .SetPath2(_GetPath( 530 FSUtils::Entry(fOldStateDirectory), 531 fOldStateDirectoryName)) 532 .SetSystemError(error); 533 } 534 535 fPackageFileManager->PackageFileMoved(package->File(), 536 fOldStateDirectoryRef); 537 package->File()->IncrementEntryRemovedIgnoreLevel(); 538 } 539 } 540 541 542 void 543 CommitTransactionHandler::_AddPackagesToActivate() 544 { 545 if (fPackagesToActivate.IsEmpty()) 546 return; 547 548 // open packages directory 549 BDirectory packagesDirectory; 550 status_t error 551 = packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef()); 552 if (error != B_OK) { 553 ERROR("Failed to open packages directory: %s\n", strerror(error)); 554 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 555 .SetPath1("<packages>") 556 .SetSystemError(error); 557 } 558 559 int32 count = fPackagesToActivate.CountItems(); 560 for (int32 i = 0; i < count; i++) { 561 Package* package = fPackagesToActivate.ItemAt(i); 562 if (fPackagesAlreadyAdded.find(package) 563 != fPackagesAlreadyAdded.end()) { 564 fAddedPackages.insert(package); 565 _PreparePackageToActivate(package); 566 continue; 567 } 568 569 // get a BEntry for the package 570 NotOwningEntryRef entryRef(fTransactionDirectoryRef, 571 package->FileName()); 572 BEntry entry; 573 error = entry.SetTo(&entryRef); 574 if (error != B_OK) { 575 ERROR("Failed to get package entry for %s: %s\n", 576 package->FileName().String(), strerror(error)); 577 throw Exception(B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH) 578 .SetPath1(package->FileName()) 579 .SetPackageName(package->FileName()) 580 .SetSystemError(error); 581 } 582 583 // move entry 584 fAddedPackages.insert(package); 585 586 error = entry.MoveTo(&packagesDirectory); 587 if (error != B_OK) { 588 fAddedPackages.erase(package); 589 ERROR("Failed to move new package %s to packages directory: %s\n", 590 package->FileName().String(), strerror(error)); 591 throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) 592 .SetPath1( 593 _GetPath(FSUtils::Entry(entryRef), package->FileName())) 594 .SetPath2(_GetPath( 595 FSUtils::Entry(packagesDirectory), 596 "packages")) 597 .SetSystemError(error); 598 } 599 600 fPackageFileManager->PackageFileMoved(package->File(), 601 fVolume->PackagesDirectoryRef()); 602 package->File()->IncrementEntryCreatedIgnoreLevel(); 603 604 // also add the package to the volume 605 fVolumeState->AddPackage(package); 606 607 _PreparePackageToActivate(package); 608 } 609 } 610 611 612 void 613 CommitTransactionHandler::_PreparePackageToActivate(Package* package) 614 { 615 fCurrentPackage = package; 616 617 // add groups 618 const BStringList& groups = package->Info().Groups(); 619 int32 count = groups.CountStrings(); 620 for (int32 i = 0; i < count; i++) 621 _AddGroup(package, groups.StringAt(i)); 622 623 // add users 624 const BObjectList<BUser>& users = package->Info().Users(); 625 for (int32 i = 0; const BUser* user = users.ItemAt(i); i++) 626 _AddUser(package, *user); 627 628 // handle global writable files 629 _AddGlobalWritableFiles(package); 630 631 fCurrentPackage = NULL; 632 } 633 634 635 void 636 CommitTransactionHandler::_AddGroup(Package* package, const BString& groupName) 637 { 638 // Check whether the group already exists. 639 char buffer[256]; 640 struct group groupBuffer; 641 struct group* groupFound; 642 int error = getgrnam_r(groupName, &groupBuffer, buffer, sizeof(buffer), 643 &groupFound); 644 if ((error == 0 && groupFound != NULL) || error == ERANGE) 645 return; 646 647 // add it 648 fAddedGroups.insert(groupName.String()); 649 650 std::string commandLine("groupadd "); 651 commandLine += FSUtils::ShellEscapeString(groupName).String(); 652 653 if (system(commandLine.c_str()) != 0) { 654 fAddedGroups.erase(groupName.String()); 655 ERROR("Failed to add group \"%s\".\n", groupName.String()); 656 throw Exception(B_TRANSACTION_FAILED_TO_ADD_GROUP) 657 .SetPackageName(package->FileName()) 658 .SetString1(groupName); 659 } 660 } 661 662 663 void 664 CommitTransactionHandler::_AddUser(Package* package, const BUser& user) 665 { 666 // Check whether the user already exists. 667 char buffer[256]; 668 struct passwd passwdBuffer; 669 struct passwd* passwdFound; 670 int error = getpwnam_r(user.Name(), &passwdBuffer, buffer, 671 sizeof(buffer), &passwdFound); 672 if ((error == 0 && passwdFound != NULL) || error == ERANGE) 673 return; 674 675 // add it 676 fAddedUsers.insert(user.Name().String()); 677 678 std::string commandLine("useradd "); 679 680 if (!user.RealName().IsEmpty()) { 681 commandLine += std::string("-n ") 682 + FSUtils::ShellEscapeString(user.RealName()).String() + " "; 683 } 684 685 if (!user.Home().IsEmpty()) { 686 commandLine += std::string("-d ") 687 + FSUtils::ShellEscapeString(user.Home()).String() + " "; 688 } 689 690 if (!user.Shell().IsEmpty()) { 691 commandLine += std::string("-s ") 692 + FSUtils::ShellEscapeString(user.Shell()).String() + " "; 693 } 694 695 if (!user.Groups().IsEmpty()) { 696 commandLine += std::string("-g ") 697 + FSUtils::ShellEscapeString(user.Groups().First()).String() 698 + " "; 699 } 700 701 commandLine += FSUtils::ShellEscapeString(user.Name()).String(); 702 703 if (system(commandLine.c_str()) != 0) { 704 fAddedUsers.erase(user.Name().String()); 705 ERROR("Failed to add user \"%s\".\n", user.Name().String()); 706 throw Exception(B_TRANSACTION_FAILED_TO_ADD_USER) 707 .SetPackageName(package->FileName()) 708 .SetString1(user.Name()); 709 710 } 711 712 // add the supplementary groups 713 int32 groupCount = user.Groups().CountStrings(); 714 for (int32 i = 1; i < groupCount; i++) { 715 commandLine = std::string("groupmod -A ") 716 + FSUtils::ShellEscapeString(user.Name()).String() 717 + " " 718 + FSUtils::ShellEscapeString(user.Groups().StringAt(i)) 719 .String(); 720 if (system(commandLine.c_str()) != 0) { 721 fAddedUsers.erase(user.Name().String()); 722 ERROR("Failed to add user \"%s\" to group \"%s\".\n", 723 user.Name().String(), user.Groups().StringAt(i).String()); 724 throw Exception(B_TRANSACTION_FAILED_TO_ADD_USER_TO_GROUP) 725 .SetPackageName(package->FileName()) 726 .SetString1(user.Name()) 727 .SetString2(user.Groups().StringAt(i)); 728 } 729 } 730 } 731 732 733 void 734 CommitTransactionHandler::_AddGlobalWritableFiles(Package* package) 735 { 736 // get the list of included files 737 const BObjectList<BGlobalWritableFileInfo>& files 738 = package->Info().GlobalWritableFileInfos(); 739 BStringList contentPaths; 740 for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); 741 i++) { 742 if (file->IsIncluded() && !contentPaths.Add(file->Path())) 743 throw std::bad_alloc(); 744 } 745 746 if (contentPaths.IsEmpty()) 747 return; 748 749 // Open the root directory of the installation location where we will 750 // extract the files -- that's the volume's root directory. 751 BDirectory rootDirectory; 752 status_t error = rootDirectory.SetTo(&fVolume->RootDirectoryRef()); 753 if (error != B_OK) { 754 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 755 .SetPath1(_GetPath( 756 FSUtils::Entry(fVolume->RootDirectoryRef()), 757 "<packagefs root>")) 758 .SetSystemError(error); 759 } 760 761 // Open writable-files directory in the administrative directory. 762 if (fWritableFilesDirectory.InitCheck() != B_OK) { 763 RelativePath directoryPath(kAdminDirectoryName, 764 kWritableFilesDirectoryName); 765 error = _OpenPackagesSubDirectory(directoryPath, true, 766 fWritableFilesDirectory); 767 768 if (error != B_OK) { 769 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 770 .SetPath1(_GetPath( 771 FSUtils::Entry(fVolume->PackagesDirectoryRef(), 772 directoryPath.ToString()), 773 directoryPath.ToString())) 774 .SetPackageName(package->FileName()) 775 .SetSystemError(error); 776 } 777 } 778 779 // extract files into a subdir of the writable-files directory 780 BDirectory extractedFilesDirectory; 781 _ExtractPackageContent(package, contentPaths, 782 fWritableFilesDirectory, extractedFilesDirectory); 783 784 for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); 785 i++) { 786 if (file->IsIncluded()) { 787 _AddGlobalWritableFile(package, *file, rootDirectory, 788 extractedFilesDirectory); 789 } 790 } 791 } 792 793 794 void 795 CommitTransactionHandler::_AddGlobalWritableFile(Package* package, 796 const BGlobalWritableFileInfo& file, const BDirectory& rootDirectory, 797 const BDirectory& extractedFilesDirectory) 798 { 799 // Map the path name to the actual target location. Currently this only 800 // concerns "settings/", which is mapped to "settings/global/". 801 BString targetPath(file.Path()); 802 if (fVolume->MountType() == PACKAGE_FS_MOUNT_TYPE_HOME) { 803 if (targetPath == "settings" 804 || targetPath.StartsWith("settings/")) { 805 targetPath.Insert("/global", 8); 806 if (targetPath.Length() == file.Path().Length()) 807 throw std::bad_alloc(); 808 } 809 } 810 811 // open parent directory of the source entry 812 const char* lastSlash = strrchr(file.Path(), '/'); 813 const BDirectory* sourceDirectory; 814 BDirectory stackSourceDirectory; 815 if (lastSlash != NULL) { 816 sourceDirectory = &stackSourceDirectory; 817 BString sourceParentPath(file.Path(), 818 lastSlash - file.Path().String()); 819 if (sourceParentPath.Length() == 0) 820 throw std::bad_alloc(); 821 822 status_t error = stackSourceDirectory.SetTo( 823 &extractedFilesDirectory, sourceParentPath); 824 if (error != B_OK) { 825 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 826 .SetPath1(_GetPath( 827 FSUtils::Entry(extractedFilesDirectory, sourceParentPath), 828 sourceParentPath)) 829 .SetPackageName(package->FileName()) 830 .SetSystemError(error); 831 } 832 } else { 833 sourceDirectory = &extractedFilesDirectory; 834 } 835 836 // open parent directory of the target entry -- create, if necessary 837 FSUtils::Path relativeSourcePath(file.Path()); 838 lastSlash = strrchr(targetPath, '/'); 839 if (lastSlash != NULL) { 840 BString targetParentPath(targetPath, 841 lastSlash - targetPath.String()); 842 if (targetParentPath.Length() == 0) 843 throw std::bad_alloc(); 844 845 BDirectory targetDirectory; 846 status_t error = FSUtils::OpenSubDirectory(rootDirectory, 847 RelativePath(targetParentPath), true, targetDirectory); 848 if (error != B_OK) { 849 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 850 .SetPath1(_GetPath( 851 FSUtils::Entry(rootDirectory, targetParentPath), 852 targetParentPath)) 853 .SetPackageName(package->FileName()) 854 .SetSystemError(error); 855 } 856 _AddGlobalWritableFileRecurse(package, *sourceDirectory, 857 relativeSourcePath, targetDirectory, lastSlash + 1, 858 file.UpdateType()); 859 } else { 860 _AddGlobalWritableFileRecurse(package, *sourceDirectory, 861 relativeSourcePath, rootDirectory, targetPath, 862 file.UpdateType()); 863 } 864 } 865 866 867 void 868 CommitTransactionHandler::_AddGlobalWritableFileRecurse(Package* package, 869 const BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath, 870 const BDirectory& targetDirectory, const char* targetName, 871 BWritableFileUpdateType updateType) 872 { 873 // * If the file doesn't exist, just copy the extracted one. 874 // * If the file does exist, compare with the previous original version: 875 // * If unchanged, just overwrite it. 876 // * If changed, leave it to the user for now. When we support merging 877 // first back the file up, then try the merge. 878 879 // Check whether the target location exists and what type the entry at 880 // both locations are. 881 struct stat targetStat; 882 if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) { 883 // target doesn't exist -- just copy 884 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " 885 "couldn't get stat for writable file, copying...\n"); 886 FSTransaction::CreateOperation copyOperation(&fFSTransaction, 887 FSUtils::Entry(targetDirectory, targetName)); 888 status_t error = BCopyEngine(BCopyEngine::COPY_RECURSIVELY) 889 .CopyEntry( 890 FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), 891 FSUtils::Entry(targetDirectory, targetName)); 892 if (error != B_OK) { 893 if (targetDirectory.GetStatFor(targetName, &targetStat) == B_OK) 894 copyOperation.Finished(); 895 896 throw Exception(B_TRANSACTION_FAILED_TO_COPY_FILE) 897 .SetPath1(_GetPath( 898 FSUtils::Entry(sourceDirectory, 899 relativeSourcePath.Leaf()), 900 relativeSourcePath)) 901 .SetPath2(_GetPath( 902 FSUtils::Entry(targetDirectory, targetName), 903 targetName)) 904 .SetSystemError(error); 905 } 906 copyOperation.Finished(); 907 return; 908 } 909 910 struct stat sourceStat; 911 status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(), 912 &sourceStat); 913 if (error != B_OK) { 914 throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) 915 .SetPath1(_GetPath( 916 FSUtils::Entry(sourceDirectory, 917 relativeSourcePath.Leaf()), 918 relativeSourcePath)) 919 .SetSystemError(error); 920 } 921 922 if ((sourceStat.st_mode & S_IFMT) != (targetStat.st_mode & S_IFMT) 923 || (!S_ISDIR(sourceStat.st_mode) && !S_ISREG(sourceStat.st_mode) 924 && !S_ISLNK(sourceStat.st_mode))) { 925 // Source and target entry types don't match or this is an entry 926 // we cannot handle. The user must handle this manually. 927 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " 928 "writable file exists, but type doesn't match previous type\n"); 929 _AddIssue(TransactionIssueBuilder( 930 BTransactionIssue::B_WRITABLE_FILE_TYPE_MISMATCH) 931 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 932 .SetPath2(FSUtils::Entry(sourceDirectory, 933 relativeSourcePath.Leaf()))); 934 return; 935 } 936 937 if (S_ISDIR(sourceStat.st_mode)) { 938 // entry is a directory -- recurse 939 BDirectory sourceSubDirectory; 940 error = sourceSubDirectory.SetTo(&sourceDirectory, 941 relativeSourcePath.Leaf()); 942 if (error != B_OK) { 943 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 944 .SetPath1(_GetPath( 945 FSUtils::Entry(sourceDirectory, 946 relativeSourcePath.Leaf()), 947 relativeSourcePath)) 948 .SetPackageName(package->FileName()) 949 .SetSystemError(error); 950 } 951 952 BDirectory targetSubDirectory; 953 error = targetSubDirectory.SetTo(&targetDirectory, targetName); 954 if (error != B_OK) { 955 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 956 .SetPath1(_GetPath( 957 FSUtils::Entry(targetDirectory, targetName), 958 targetName)) 959 .SetPackageName(package->FileName()) 960 .SetSystemError(error); 961 } 962 963 entry_ref entry; 964 while (sourceSubDirectory.GetNextRef(&entry) == B_OK) { 965 relativeSourcePath.AppendComponent(entry.name); 966 _AddGlobalWritableFileRecurse(package, sourceSubDirectory, 967 relativeSourcePath, targetSubDirectory, entry.name, 968 updateType); 969 relativeSourcePath.RemoveLastComponent(); 970 } 971 972 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " 973 "writable directory, recursion done\n"); 974 return; 975 } 976 977 // get the package the target file originated from 978 BString originalPackage; 979 if (BNode(&targetDirectory, targetName).ReadAttrString( 980 kPackageFileAttribute, &originalPackage) != B_OK) { 981 // Can't determine the original package. The user must handle this 982 // manually. 983 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " 984 "failed to get SYS:PACKAGE attribute\n"); 985 if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) { 986 _AddIssue(TransactionIssueBuilder( 987 BTransactionIssue::B_WRITABLE_FILE_NO_PACKAGE_ATTRIBUTE) 988 .SetPath1(FSUtils::Entry(targetDirectory, targetName))); 989 } 990 return; 991 } 992 993 // If that's our package, we're happy. 994 if (originalPackage == package->RevisionedNameThrows()) { 995 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " 996 "file tagged with same package version we're activating\n"); 997 return; 998 } 999 1000 // Check, whether the writable-files directory for the original package 1001 // exists. 1002 BString originalRelativeSourcePath = BString().SetToFormat("%s/%s", 1003 originalPackage.String(), relativeSourcePath.ToCString()); 1004 if (originalRelativeSourcePath.IsEmpty()) 1005 throw std::bad_alloc(); 1006 1007 struct stat originalPackageStat; 1008 error = fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath, 1009 &originalPackageStat); 1010 if (error != B_OK 1011 || (sourceStat.st_mode & S_IFMT) 1012 != (originalPackageStat.st_mode & S_IFMT)) { 1013 // Original entry doesn't exist (either we don't have the data from 1014 // the original package or the entry really didn't exist) or its 1015 // type differs from the expected one. The user must handle this 1016 // manually. 1017 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " 1018 "original \"%s\" doesn't exist or has other type\n", 1019 _GetPath(FSUtils::Entry(fWritableFilesDirectory, 1020 originalRelativeSourcePath), 1021 originalRelativeSourcePath).String()); 1022 if (error != B_OK) { 1023 _AddIssue(TransactionIssueBuilder( 1024 BTransactionIssue 1025 ::B_WRITABLE_FILE_OLD_ORIGINAL_FILE_MISSING) 1026 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 1027 .SetPath2(FSUtils::Entry(fWritableFilesDirectory, 1028 originalRelativeSourcePath))); 1029 } else { 1030 _AddIssue(TransactionIssueBuilder( 1031 BTransactionIssue 1032 ::B_WRITABLE_FILE_OLD_ORIGINAL_FILE_TYPE_MISMATCH) 1033 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 1034 .SetPath2(FSUtils::Entry(fWritableFilesDirectory, 1035 originalRelativeSourcePath))); 1036 } 1037 return; 1038 } 1039 1040 if (S_ISREG(sourceStat.st_mode)) { 1041 // compare file content 1042 bool equal; 1043 error = FSUtils::CompareFileContent( 1044 FSUtils::Entry(fWritableFilesDirectory, 1045 originalRelativeSourcePath), 1046 FSUtils::Entry(targetDirectory, targetName), 1047 equal); 1048 // TODO: Merge support! 1049 if (error != B_OK || !equal) { 1050 // The comparison failed or the files differ. The user must 1051 // handle this manually. 1052 PRINT("Volume::CommitTransactionHandler::" 1053 "_AddGlobalWritableFile(): " 1054 "file comparison failed (%s) or files aren't equal\n", 1055 strerror(error)); 1056 if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) { 1057 if (error != B_OK) { 1058 _AddIssue(TransactionIssueBuilder( 1059 BTransactionIssue 1060 ::B_WRITABLE_FILE_COMPARISON_FAILED) 1061 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 1062 .SetPath2(FSUtils::Entry(fWritableFilesDirectory, 1063 originalRelativeSourcePath)) 1064 .SetSystemError(error)); 1065 } else { 1066 _AddIssue(TransactionIssueBuilder( 1067 BTransactionIssue 1068 ::B_WRITABLE_FILE_NOT_EQUAL) 1069 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 1070 .SetPath2(FSUtils::Entry(fWritableFilesDirectory, 1071 originalRelativeSourcePath))); 1072 } 1073 } 1074 return; 1075 } 1076 } else { 1077 // compare symlinks 1078 bool equal; 1079 error = FSUtils::CompareSymLinks( 1080 FSUtils::Entry(fWritableFilesDirectory, 1081 originalRelativeSourcePath), 1082 FSUtils::Entry(targetDirectory, targetName), 1083 equal); 1084 if (error != B_OK || !equal) { 1085 // The comparison failed or the symlinks differ. The user must 1086 // handle this manually. 1087 PRINT("Volume::CommitTransactionHandler::" 1088 "_AddGlobalWritableFile(): " 1089 "symlink comparison failed (%s) or symlinks aren't equal\n", 1090 strerror(error)); 1091 if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) { 1092 if (error != B_OK) { 1093 _AddIssue(TransactionIssueBuilder( 1094 BTransactionIssue 1095 ::B_WRITABLE_SYMLINK_COMPARISON_FAILED) 1096 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 1097 .SetPath2(FSUtils::Entry(fWritableFilesDirectory, 1098 originalRelativeSourcePath)) 1099 .SetSystemError(error)); 1100 } else { 1101 _AddIssue(TransactionIssueBuilder( 1102 BTransactionIssue 1103 ::B_WRITABLE_SYMLINK_NOT_EQUAL) 1104 .SetPath1(FSUtils::Entry(targetDirectory, targetName)) 1105 .SetPath2(FSUtils::Entry(fWritableFilesDirectory, 1106 originalRelativeSourcePath))); 1107 } 1108 } 1109 return; 1110 } 1111 } 1112 1113 // Replace the existing file/symlink. We do that in two steps: First 1114 // copy the new file to a neighoring location, then move-replace the 1115 // old file. 1116 BString tempTargetName; 1117 tempTargetName.SetToFormat("%s.%s", targetName, 1118 package->RevisionedNameThrows().String()); 1119 if (tempTargetName.IsEmpty()) 1120 throw std::bad_alloc(); 1121 1122 // copy 1123 FSTransaction::CreateOperation copyOperation(&fFSTransaction, 1124 FSUtils::Entry(targetDirectory, tempTargetName)); 1125 1126 error = BCopyEngine(BCopyEngine::UNLINK_DESTINATION).CopyEntry( 1127 FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), 1128 FSUtils::Entry(targetDirectory, tempTargetName)); 1129 if (error != B_OK) { 1130 throw Exception(B_TRANSACTION_FAILED_TO_COPY_FILE) 1131 .SetPath1(_GetPath( 1132 FSUtils::Entry(sourceDirectory, 1133 relativeSourcePath.Leaf()), 1134 relativeSourcePath)) 1135 .SetPath2(_GetPath( 1136 FSUtils::Entry(targetDirectory, tempTargetName), 1137 tempTargetName)) 1138 .SetSystemError(error); 1139 } 1140 1141 copyOperation.Finished(); 1142 1143 // rename 1144 FSTransaction::RemoveOperation renameOperation(&fFSTransaction, 1145 FSUtils::Entry(targetDirectory, targetName), 1146 FSUtils::Entry(fWritableFilesDirectory, 1147 originalRelativeSourcePath)); 1148 1149 BEntry targetEntry; 1150 error = targetEntry.SetTo(&targetDirectory, tempTargetName); 1151 if (error == B_OK) 1152 error = targetEntry.Rename(targetName, true); 1153 if (error != B_OK) { 1154 throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) 1155 .SetPath1(_GetPath( 1156 FSUtils::Entry(targetDirectory, tempTargetName), 1157 tempTargetName)) 1158 .SetPath2(targetName) 1159 .SetSystemError(error); 1160 } 1161 1162 renameOperation.Finished(); 1163 copyOperation.Unregister(); 1164 } 1165 1166 1167 void 1168 CommitTransactionHandler::_RevertAddPackagesToActivate() 1169 { 1170 if (fAddedPackages.empty()) 1171 return; 1172 1173 // open transaction directory 1174 BDirectory transactionDirectory; 1175 status_t error = transactionDirectory.SetTo(&fTransactionDirectoryRef); 1176 if (error != B_OK) { 1177 ERROR("failed to open transaction directory: %s\n", 1178 strerror(error)); 1179 } 1180 1181 for (PackageSet::iterator it = fAddedPackages.begin(); 1182 it != fAddedPackages.end(); ++it) { 1183 // remove package from the volume 1184 Package* package = *it; 1185 1186 if (fPackagesAlreadyAdded.find(package) 1187 != fPackagesAlreadyAdded.end()) { 1188 continue; 1189 } 1190 1191 fVolumeState->RemovePackage(package); 1192 1193 if (transactionDirectory.InitCheck() != B_OK) 1194 continue; 1195 1196 // get BEntry for the package 1197 NotOwningEntryRef entryRef(package->EntryRef()); 1198 BEntry entry; 1199 error = entry.SetTo(&entryRef); 1200 if (error != B_OK) { 1201 ERROR("failed to get entry for package \"%s\": %s\n", 1202 package->FileName().String(), strerror(error)); 1203 continue; 1204 } 1205 1206 // move entry 1207 error = entry.MoveTo(&transactionDirectory); 1208 if (error != B_OK) { 1209 ERROR("failed to move new package \"%s\" back to transaction " 1210 "directory: %s\n", package->FileName().String(), 1211 strerror(error)); 1212 continue; 1213 } 1214 1215 fPackageFileManager->PackageFileMoved(package->File(), 1216 fTransactionDirectoryRef); 1217 package->File()->IncrementEntryRemovedIgnoreLevel(); 1218 } 1219 } 1220 1221 1222 void 1223 CommitTransactionHandler::_RevertRemovePackagesToDeactivate() 1224 { 1225 if (fRemovedPackages.empty()) 1226 return; 1227 1228 // open packages directory 1229 BDirectory packagesDirectory; 1230 status_t error 1231 = packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef()); 1232 if (error != B_OK) { 1233 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 1234 .SetPath1("<packages>") 1235 .SetSystemError(error); 1236 } 1237 1238 for (PackageSet::iterator it = fRemovedPackages.begin(); 1239 it != fRemovedPackages.end(); ++it) { 1240 Package* package = *it; 1241 if (fPackagesAlreadyRemoved.find(package) 1242 != fPackagesAlreadyRemoved.end()) { 1243 continue; 1244 } 1245 1246 // get a BEntry for the package 1247 BEntry entry; 1248 status_t error = entry.SetTo(&fOldStateDirectory, 1249 package->FileName()); 1250 if (error != B_OK) { 1251 ERROR("failed to get entry for package \"%s\": %s\n", 1252 package->FileName().String(), strerror(error)); 1253 continue; 1254 } 1255 1256 // move entry 1257 error = entry.MoveTo(&packagesDirectory); 1258 if (error != B_OK) { 1259 ERROR("failed to move old package \"%s\" back to packages " 1260 "directory: %s\n", package->FileName().String(), 1261 strerror(error)); 1262 continue; 1263 } 1264 1265 fPackageFileManager->PackageFileMoved(package->File(), 1266 fVolume->PackagesDirectoryRef()); 1267 package->File()->IncrementEntryCreatedIgnoreLevel(); 1268 } 1269 } 1270 1271 1272 void 1273 CommitTransactionHandler::_RevertUserGroupChanges() 1274 { 1275 // delete users 1276 for (StringSet::const_iterator it = fAddedUsers.begin(); 1277 it != fAddedUsers.end(); ++it) { 1278 std::string commandLine("userdel "); 1279 commandLine += FSUtils::ShellEscapeString(it->c_str()).String(); 1280 if (system(commandLine.c_str()) != 0) 1281 ERROR("failed to remove user \"%s\"\n", it->c_str()); 1282 } 1283 1284 // delete groups 1285 for (StringSet::const_iterator it = fAddedGroups.begin(); 1286 it != fAddedGroups.end(); ++it) { 1287 std::string commandLine("groupdel "); 1288 commandLine += FSUtils::ShellEscapeString(it->c_str()).String(); 1289 if (system(commandLine.c_str()) != 0) 1290 ERROR("failed to remove group \"%s\"\n", it->c_str()); 1291 } 1292 } 1293 1294 1295 void 1296 CommitTransactionHandler::_RunPostInstallScripts() 1297 { 1298 for (PackageSet::iterator it = fAddedPackages.begin(); 1299 it != fAddedPackages.end(); ++it) { 1300 Package* package = *it; 1301 fCurrentPackage = package; 1302 const BStringList& scripts = package->Info().PostInstallScripts(); 1303 int32 count = scripts.CountStrings(); 1304 for (int32 i = 0; i < count; i++) 1305 _RunPostInstallScript(package, scripts.StringAt(i)); 1306 } 1307 1308 fCurrentPackage = NULL; 1309 } 1310 1311 1312 void 1313 CommitTransactionHandler::_RunPostInstallScript(Package* package, 1314 const BString& script) 1315 { 1316 BDirectory rootDir(&fVolume->RootDirectoryRef()); 1317 BPath scriptPath(&rootDir, script); 1318 status_t error = scriptPath.InitCheck(); 1319 if (error != B_OK) { 1320 ERROR("Volume::CommitTransactionHandler::_RunPostInstallScript(): " 1321 "failed get path of post-installation script \"%s\" of package " 1322 "%s: %s\n", script.String(), package->FileName().String(), 1323 strerror(error)); 1324 _AddIssue(TransactionIssueBuilder( 1325 BTransactionIssue::B_POST_INSTALL_SCRIPT_NOT_FOUND) 1326 .SetPath1(script) 1327 .SetSystemError(error)); 1328 return; 1329 } 1330 1331 errno = 0; 1332 int result = system(scriptPath.Path()); 1333 if (result != 0) { 1334 ERROR("Volume::CommitTransactionHandler::_RunPostInstallScript(): " 1335 "running post-installation script \"%s\" of package %s " 1336 "failed: %d (errno: %s)\n", script.String(), 1337 package->FileName().String(), result, 1338 strerror(errno)); 1339 if (result < 0 && errno != 0) { 1340 _AddIssue(TransactionIssueBuilder( 1341 BTransactionIssue::B_POST_INSTALL_SCRIPT_FAILED) 1342 .SetPath1(BString(scriptPath.Path())) 1343 .SetSystemError(errno)); 1344 } else { 1345 _AddIssue(TransactionIssueBuilder( 1346 BTransactionIssue::B_STARTING_POST_INSTALL_SCRIPT_FAILED) 1347 .SetPath1(BString(scriptPath.Path())) 1348 .SetExitCode(result)); 1349 } 1350 } 1351 } 1352 1353 1354 void 1355 CommitTransactionHandler::_ExtractPackageContent(Package* package, 1356 const BStringList& contentPaths, BDirectory& targetDirectory, 1357 BDirectory& _extractedFilesDirectory) 1358 { 1359 // check whether the subdirectory already exists 1360 BString targetName(package->RevisionedNameThrows()); 1361 1362 BEntry targetEntry; 1363 status_t error = targetEntry.SetTo(&targetDirectory, targetName); 1364 if (error != B_OK) { 1365 throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) 1366 .SetPath1(_GetPath( 1367 FSUtils::Entry(targetDirectory, targetName), 1368 targetName)) 1369 .SetPackageName(package->FileName()) 1370 .SetSystemError(error); 1371 } 1372 if (targetEntry.Exists()) { 1373 // nothing to do -- the very same version of the package has already 1374 // been extracted 1375 error = _extractedFilesDirectory.SetTo(&targetDirectory, 1376 targetName); 1377 if (error != B_OK) { 1378 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 1379 .SetPath1(_GetPath( 1380 FSUtils::Entry(targetDirectory, targetName), 1381 targetName)) 1382 .SetPackageName(package->FileName()) 1383 .SetSystemError(error); 1384 } 1385 return; 1386 } 1387 1388 // create the subdirectory with a temporary name (remove, if it already 1389 // exists) 1390 BString temporaryTargetName = BString().SetToFormat("%s.tmp", 1391 targetName.String()); 1392 if (temporaryTargetName.IsEmpty()) 1393 throw std::bad_alloc(); 1394 1395 error = targetEntry.SetTo(&targetDirectory, temporaryTargetName); 1396 if (error != B_OK) { 1397 throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) 1398 .SetPath1(_GetPath( 1399 FSUtils::Entry(targetDirectory, temporaryTargetName), 1400 temporaryTargetName)) 1401 .SetPackageName(package->FileName()) 1402 .SetSystemError(error); 1403 } 1404 1405 if (targetEntry.Exists()) { 1406 // remove pre-existing 1407 error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry)); 1408 if (error != B_OK) { 1409 throw Exception(B_TRANSACTION_FAILED_TO_REMOVE_DIRECTORY) 1410 .SetPath1(_GetPath( 1411 FSUtils::Entry(targetDirectory, temporaryTargetName), 1412 temporaryTargetName)) 1413 .SetPackageName(package->FileName()) 1414 .SetSystemError(error); 1415 } 1416 } 1417 1418 BDirectory& subDirectory = _extractedFilesDirectory; 1419 FSTransaction::CreateOperation createSubDirectoryOperation( 1420 &fFSTransaction, 1421 FSUtils::Entry(targetDirectory, temporaryTargetName)); 1422 error = targetDirectory.CreateDirectory(temporaryTargetName, 1423 &subDirectory); 1424 if (error != B_OK) { 1425 throw Exception(B_TRANSACTION_FAILED_TO_CREATE_DIRECTORY) 1426 .SetPath1(_GetPath( 1427 FSUtils::Entry(targetDirectory, temporaryTargetName), 1428 temporaryTargetName)) 1429 .SetPackageName(package->FileName()) 1430 .SetSystemError(error); 1431 } 1432 1433 createSubDirectoryOperation.Finished(); 1434 1435 // extract 1436 NotOwningEntryRef packageRef(package->EntryRef()); 1437 1438 int32 contentPathCount = contentPaths.CountStrings(); 1439 for (int32 i = 0; i < contentPathCount; i++) { 1440 const char* contentPath = contentPaths.StringAt(i); 1441 1442 error = FSUtils::ExtractPackageContent(FSUtils::Entry(packageRef), 1443 contentPath, FSUtils::Entry(subDirectory)); 1444 if (error != B_OK) { 1445 throw Exception(B_TRANSACTION_FAILED_TO_EXTRACT_PACKAGE_FILE) 1446 .SetPath1(contentPath) 1447 .SetPackageName(package->FileName()) 1448 .SetSystemError(error); 1449 } 1450 } 1451 1452 // tag all entries with the package attribute 1453 _TagPackageEntriesRecursively(subDirectory, targetName, true); 1454 1455 // rename the subdirectory 1456 error = targetEntry.Rename(targetName); 1457 if (error != B_OK) { 1458 throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) 1459 .SetPath1(_GetPath( 1460 FSUtils::Entry(targetDirectory, temporaryTargetName), 1461 temporaryTargetName)) 1462 .SetPath2(targetName) 1463 .SetPackageName(package->FileName()) 1464 .SetSystemError(error); 1465 } 1466 1467 // keep the directory, regardless of whether the transaction is rolled 1468 // back 1469 createSubDirectoryOperation.Unregister(); 1470 } 1471 1472 1473 status_t 1474 CommitTransactionHandler::_OpenPackagesSubDirectory(const RelativePath& path, 1475 bool create, BDirectory& _directory) 1476 { 1477 // open the packages directory 1478 BDirectory directory; 1479 status_t error = directory.SetTo(&fVolume->PackagesDirectoryRef()); 1480 if (error != B_OK) { 1481 ERROR("CommitTransactionHandler::_OpenPackagesSubDirectory(): failed " 1482 "to open packages directory: %s\n", strerror(error)); 1483 RETURN_ERROR(error); 1484 } 1485 1486 return FSUtils::OpenSubDirectory(directory, path, create, _directory); 1487 } 1488 1489 1490 status_t 1491 CommitTransactionHandler::_OpenPackagesFile( 1492 const RelativePath& subDirectoryPath, const char* fileName, uint32 openMode, 1493 BFile& _file, BEntry* _entry) 1494 { 1495 BDirectory directory; 1496 if (!subDirectoryPath.IsEmpty()) { 1497 status_t error = _OpenPackagesSubDirectory(subDirectoryPath, 1498 (openMode & B_CREATE_FILE) != 0, directory); 1499 if (error != B_OK) { 1500 ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to " 1501 "open packages subdirectory \"%s\": %s\n", 1502 subDirectoryPath.ToString().String(), strerror(error)); 1503 RETURN_ERROR(error); 1504 } 1505 } else { 1506 status_t error = directory.SetTo(&fVolume->PackagesDirectoryRef()); 1507 if (error != B_OK) { 1508 ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to " 1509 "open packages directory: %s\n", strerror(error)); 1510 RETURN_ERROR(error); 1511 } 1512 } 1513 1514 BEntry stackEntry; 1515 BEntry& entry = _entry != NULL ? *_entry : stackEntry; 1516 status_t error = entry.SetTo(&directory, fileName); 1517 if (error != B_OK) { 1518 ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to get " 1519 "entry for file: %s", strerror(error)); 1520 RETURN_ERROR(error); 1521 } 1522 1523 return _file.SetTo(&entry, openMode); 1524 } 1525 1526 1527 void 1528 CommitTransactionHandler::_WriteActivationFile( 1529 const RelativePath& directoryPath, const char* fileName, 1530 const PackageSet& toActivate, const PackageSet& toDeactivate, 1531 BEntry& _entry) 1532 { 1533 // create the content 1534 BString activationFileContent; 1535 _CreateActivationFileContent(toActivate, toDeactivate, 1536 activationFileContent); 1537 1538 // write the file 1539 status_t error = _WriteTextFile(directoryPath, fileName, 1540 activationFileContent, _entry); 1541 if (error != B_OK) { 1542 BString filePath = directoryPath.ToString() << '/' << fileName; 1543 throw Exception(B_TRANSACTION_FAILED_TO_WRITE_ACTIVATION_FILE) 1544 .SetPath1(_GetPath( 1545 FSUtils::Entry(fVolume->PackagesDirectoryRef(), filePath), 1546 filePath)) 1547 .SetSystemError(error); 1548 } 1549 } 1550 1551 1552 void 1553 CommitTransactionHandler::_CreateActivationFileContent( 1554 const PackageSet& toActivate, const PackageSet& toDeactivate, 1555 BString& _content) 1556 { 1557 BString activationFileContent; 1558 for (PackageFileNameHashTable::Iterator it 1559 = fVolumeState->ByFileNameIterator(); 1560 Package* package = it.Next();) { 1561 if (package->IsActive() 1562 && toDeactivate.find(package) == toDeactivate.end()) { 1563 int32 length = activationFileContent.Length(); 1564 activationFileContent << package->FileName() << '\n'; 1565 if (activationFileContent.Length() 1566 < length + package->FileName().Length() + 1) { 1567 throw Exception(B_TRANSACTION_NO_MEMORY); 1568 } 1569 } 1570 } 1571 1572 for (PackageSet::const_iterator it = toActivate.begin(); 1573 it != toActivate.end(); ++it) { 1574 Package* package = *it; 1575 int32 length = activationFileContent.Length(); 1576 activationFileContent << package->FileName() << '\n'; 1577 if (activationFileContent.Length() 1578 < length + package->FileName().Length() + 1) { 1579 throw Exception(B_TRANSACTION_NO_MEMORY); 1580 } 1581 } 1582 1583 _content = activationFileContent; 1584 } 1585 1586 1587 status_t 1588 CommitTransactionHandler::_WriteTextFile(const RelativePath& directoryPath, 1589 const char* fileName, const BString& content, BEntry& _entry) 1590 { 1591 BFile file; 1592 status_t error = _OpenPackagesFile(directoryPath, 1593 fileName, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE, file, &_entry); 1594 if (error != B_OK) { 1595 ERROR("CommitTransactionHandler::_WriteTextFile(): failed to create " 1596 "file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName, 1597 strerror(error)); 1598 return error; 1599 } 1600 1601 ssize_t bytesWritten = file.Write(content.String(), 1602 content.Length()); 1603 if (bytesWritten < 0) { 1604 ERROR("CommitTransactionHandler::_WriteTextFile(): failed to write " 1605 "file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName, 1606 strerror(bytesWritten)); 1607 return bytesWritten; 1608 } 1609 1610 return B_OK; 1611 } 1612 1613 1614 void 1615 CommitTransactionHandler::_ChangePackageActivation( 1616 const PackageSet& packagesToActivate, 1617 const PackageSet& packagesToDeactivate) 1618 { 1619 INFORM("CommitTransactionHandler::_ChangePackageActivation(): activating " 1620 "%zu, deactivating %zu packages\n", packagesToActivate.size(), 1621 packagesToDeactivate.size()); 1622 1623 // write the temporary package activation file 1624 BEntry activationFileEntry; 1625 _WriteActivationFile(RelativePath(kAdminDirectoryName), 1626 kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate, 1627 activationFileEntry); 1628 1629 // notify packagefs 1630 if (fVolumeStateIsActive) { 1631 _ChangePackageActivationIOCtl(packagesToActivate, packagesToDeactivate); 1632 } else { 1633 // TODO: Notify packagefs that active packages have been moved or do 1634 // node monitoring in packagefs! 1635 } 1636 1637 // rename the temporary activation file to the final file 1638 status_t error = activationFileEntry.Rename(kActivationFileName, true); 1639 if (error != B_OK) { 1640 throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) 1641 .SetPath1(_GetPath( 1642 FSUtils::Entry(activationFileEntry), 1643 activationFileEntry.Name())) 1644 .SetPath2(kActivationFileName) 1645 .SetSystemError(error); 1646 1647 // TODO: We should probably try to revert the activation changes, though that 1648 // will fail, if this method has been called in response to node monitoring 1649 // events. Alternatively moving the package activation file could be made part 1650 // of the ioctl(), since packagefs should be able to undo package changes until 1651 // the very end, unless running out of memory. In the end the situation would be 1652 // bad anyway, though, since the activation file may refer to removed packages 1653 // and things would be in an inconsistent state after rebooting. 1654 } 1655 1656 // Update our state, i.e. remove deactivated packages and mark activated 1657 // packages accordingly. 1658 fVolumeState->ActivationChanged(packagesToActivate, packagesToDeactivate); 1659 } 1660 1661 1662 void 1663 CommitTransactionHandler::_ChangePackageActivationIOCtl( 1664 const PackageSet& packagesToActivate, 1665 const PackageSet& packagesToDeactivate) 1666 { 1667 // compute the size of the allocation we need for the activation change 1668 // request 1669 int32 itemCount = packagesToActivate.size() + packagesToDeactivate.size(); 1670 size_t requestSize = sizeof(PackageFSActivationChangeRequest) 1671 + itemCount * sizeof(PackageFSActivationChangeItem); 1672 1673 for (PackageSet::iterator it = packagesToActivate.begin(); 1674 it != packagesToActivate.end(); ++it) { 1675 requestSize += (*it)->FileName().Length() + 1; 1676 } 1677 1678 for (PackageSet::iterator it = packagesToDeactivate.begin(); 1679 it != packagesToDeactivate.end(); ++it) { 1680 requestSize += (*it)->FileName().Length() + 1; 1681 } 1682 1683 // allocate and prepare the request 1684 PackageFSActivationChangeRequest* request 1685 = (PackageFSActivationChangeRequest*)malloc(requestSize); 1686 if (request == NULL) 1687 throw Exception(B_TRANSACTION_NO_MEMORY); 1688 MemoryDeleter requestDeleter(request); 1689 1690 request->itemCount = itemCount; 1691 1692 PackageFSActivationChangeItem* item = &request->items[0]; 1693 char* nameBuffer = (char*)(item + itemCount); 1694 1695 for (PackageSet::iterator it = packagesToActivate.begin(); 1696 it != packagesToActivate.end(); ++it, item++) { 1697 _FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it, 1698 nameBuffer); 1699 } 1700 1701 for (PackageSet::iterator it = packagesToDeactivate.begin(); 1702 it != packagesToDeactivate.end(); ++it, item++) { 1703 _FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it, 1704 nameBuffer); 1705 } 1706 1707 // issue the request 1708 int fd = fVolume->OpenRootDirectory(); 1709 if (fd < 0) { 1710 throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) 1711 .SetPath1(_GetPath( 1712 FSUtils::Entry(fVolume->RootDirectoryRef()), 1713 "<packagefs root>")) 1714 .SetSystemError(fd); 1715 } 1716 FileDescriptorCloser fdCloser(fd); 1717 1718 if (ioctl(fd, PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request, requestSize) 1719 != 0) { 1720 // TODO: We need more error information and error handling! 1721 throw Exception(B_TRANSACTION_FAILED_TO_CHANGE_PACKAGE_ACTIVATION) 1722 .SetSystemError(errno); 1723 } 1724 } 1725 1726 1727 void 1728 CommitTransactionHandler::_FillInActivationChangeItem( 1729 PackageFSActivationChangeItem* item, PackageFSActivationChangeType type, 1730 Package* package, char*& nameBuffer) 1731 { 1732 item->type = type; 1733 item->packageDeviceID = package->NodeRef().device; 1734 item->packageNodeID = package->NodeRef().node; 1735 item->nameLength = package->FileName().Length(); 1736 item->parentDeviceID = fVolume->PackagesDeviceID(); 1737 item->parentDirectoryID = fVolume->PackagesDirectoryID(); 1738 item->name = nameBuffer; 1739 strcpy(nameBuffer, package->FileName()); 1740 nameBuffer += package->FileName().Length() + 1; 1741 } 1742 1743 1744 bool 1745 CommitTransactionHandler::_IsSystemPackage(Package* package) 1746 { 1747 // package name should be "haiku[_<arch>]" 1748 const BString& name = package->Info().Name(); 1749 if (!name.StartsWith("haiku")) 1750 return false; 1751 if (name.Length() == 5) 1752 return true; 1753 if (name[5] != '_') 1754 return false; 1755 1756 BPackageArchitecture architecture; 1757 return BPackageInfo::GetArchitectureByName(name.String() + 6, architecture) 1758 == B_OK; 1759 } 1760 1761 1762 void 1763 CommitTransactionHandler::_AddIssue(const TransactionIssueBuilder& builder) 1764 { 1765 fResult.AddIssue(builder.BuildIssue(fCurrentPackage)); 1766 } 1767 1768 1769 /*static*/ BString 1770 CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry, 1771 const BString& fallback) 1772 { 1773 BString path = entry.Path(); 1774 return path.IsEmpty() ? fallback : path; 1775 } 1776 1777 1778 /*static*/ void 1779 CommitTransactionHandler::_TagPackageEntriesRecursively(BDirectory& directory, 1780 const BString& value, bool nonDirectoriesOnly) 1781 { 1782 char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; 1783 dirent *entry = (dirent*)buffer; 1784 while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) { 1785 if (strcmp(entry->d_name, ".") == 0 1786 || strcmp(entry->d_name, "..") == 0) { 1787 continue; 1788 } 1789 1790 // determine type 1791 struct stat st; 1792 status_t error = directory.GetStatFor(entry->d_name, &st); 1793 if (error != B_OK) { 1794 throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) 1795 .SetPath1(_GetPath( 1796 FSUtils::Entry(directory, entry->d_name), 1797 entry->d_name)) 1798 .SetSystemError(error); 1799 } 1800 bool isDirectory = S_ISDIR(st.st_mode); 1801 1802 // open the node and set the attribute 1803 BNode stackNode; 1804 BDirectory stackDirectory; 1805 BNode* node; 1806 if (isDirectory) { 1807 node = &stackDirectory; 1808 error = stackDirectory.SetTo(&directory, entry->d_name); 1809 } else { 1810 node = &stackNode; 1811 error = stackNode.SetTo(&directory, entry->d_name); 1812 } 1813 1814 if (error != B_OK) { 1815 throw Exception(isDirectory 1816 ? B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY 1817 : B_TRANSACTION_FAILED_TO_OPEN_FILE) 1818 .SetPath1(_GetPath( 1819 FSUtils::Entry(directory, entry->d_name), 1820 entry->d_name)) 1821 .SetSystemError(error); 1822 } 1823 1824 if (!isDirectory || !nonDirectoriesOnly) { 1825 error = node->WriteAttrString(kPackageFileAttribute, &value); 1826 if (error != B_OK) { 1827 throw Exception(B_TRANSACTION_FAILED_TO_WRITE_FILE_ATTRIBUTE) 1828 .SetPath1(_GetPath( 1829 FSUtils::Entry(directory, entry->d_name), 1830 entry->d_name)) 1831 .SetSystemError(error); 1832 } 1833 } 1834 1835 // recurse 1836 if (isDirectory) { 1837 _TagPackageEntriesRecursively(stackDirectory, value, 1838 nonDirectoriesOnly); 1839 } 1840 } 1841 } 1842