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