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