btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / servers / package / CommitTransactionHandler.cpp
blobea37e1aeee4ade9cc3ef665821feac5793148d7a
1 /*
2 * Copyright 2013-2014, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Ingo Weinhold <ingo_weinhold@gmx.de>
7 */
10 #include "CommitTransactionHandler.h"
12 #include <errno.h>
13 #include <grp.h>
14 #include <pwd.h>
16 #include <File.h>
17 #include <Path.h>
18 #include <SymLink.h>
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>
27 #include "Constants.h"
28 #include "DebugSupport.h"
29 #include "Exception.h"
30 #include "PackageFileManager.h"
31 #include "VolumeState.h"
34 using namespace BPackageKit::BPrivate;
36 using BPackageKit::BTransactionIssue;
39 // #pragma mark - TransactionIssueBuilder
42 struct CommitTransactionHandler::TransactionIssueBuilder {
43 TransactionIssueBuilder(BTransactionIssue::BType type,
44 Package* package = NULL)
46 fType(type),
47 fPackageName(package != NULL ? package->FileName() : BString()),
48 fPath1(),
49 fPath2(),
50 fSystemError(B_OK),
51 fExitCode(0)
55 TransactionIssueBuilder& SetPath1(const BString& path)
57 fPath1 = path;
58 return *this;
61 TransactionIssueBuilder& SetPath1(const FSUtils::Entry& entry)
63 return SetPath1(entry.Path());
66 TransactionIssueBuilder& SetPath2(const BString& path)
68 fPath2 = path;
69 return *this;
72 TransactionIssueBuilder& SetPath2(const FSUtils::Entry& entry)
74 return SetPath2(entry.Path());
77 TransactionIssueBuilder& SetSystemError(status_t error)
79 fSystemError = error;
80 return *this;
83 TransactionIssueBuilder& SetExitCode(int exitCode)
85 fExitCode = exitCode;
86 return *this;
89 BTransactionIssue BuildIssue(Package* package) const
91 BString packageName(fPackageName);
92 if (packageName.IsEmpty() && package != NULL)
93 packageName = package->FileName();
95 return BTransactionIssue(fType, packageName, fPath1, fPath2,
96 fSystemError, fExitCode);
99 private:
100 BTransactionIssue::BType fType;
101 BString fPackageName;
102 BString fPath1;
103 BString fPath2;
104 status_t fSystemError;
105 int fExitCode;
109 // #pragma mark - CommitTransactionHandler
112 CommitTransactionHandler::CommitTransactionHandler(Volume* volume,
113 PackageFileManager* packageFileManager, BCommitTransactionResult& result)
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)
139 CommitTransactionHandler::~CommitTransactionHandler()
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;
152 delete fVolumeState;
156 void
157 CommitTransactionHandler::Init(VolumeState* volumeState,
158 bool isActiveVolumeState, const PackageSet& packagesAlreadyAdded,
159 const PackageSet& packagesAlreadyRemoved)
161 fVolumeState = volumeState->Clone();
162 if (fVolumeState == NULL)
163 throw std::bad_alloc();
165 fVolumeStateIsActive = isActiveVolumeState;
167 for (PackageSet::const_iterator it = packagesAlreadyAdded.begin();
168 it != packagesAlreadyAdded.end(); ++it) {
169 Package* package = fVolumeState->FindPackage((*it)->FileName());
170 fPackagesAlreadyAdded.insert(package);
173 for (PackageSet::const_iterator it = packagesAlreadyRemoved.begin();
174 it != packagesAlreadyRemoved.end(); ++it) {
175 Package* package = fVolumeState->FindPackage((*it)->FileName());
176 fPackagesAlreadyRemoved.insert(package);
181 void
182 CommitTransactionHandler::HandleRequest(BMessage* request)
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);
194 HandleRequest(transaction);
198 void
199 CommitTransactionHandler::HandleRequest(
200 const BActivationTransaction& transaction)
202 // check the change count
203 if (transaction.ChangeCount() != fVolume->ChangeCount())
204 throw Exception(B_TRANSACTION_CHANGE_COUNT_MISMATCH);
206 // collect the packages to deactivate
207 _GetPackagesToDeactivate(transaction);
209 // read the packages to activate
210 _ReadPackagesToActivate(transaction);
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);
219 _ApplyChanges();
223 void
224 CommitTransactionHandler::HandleRequest()
226 for (PackageSet::const_iterator it = fPackagesAlreadyAdded.begin();
227 it != fPackagesAlreadyAdded.end(); ++it) {
228 if (!fPackagesToActivate.AddItem(*it))
229 throw std::bad_alloc();
232 fPackagesToDeactivate = fPackagesAlreadyRemoved;
234 _ApplyChanges();
238 void
239 CommitTransactionHandler::Revert()
241 // move packages to activate back to transaction directory
242 _RevertAddPackagesToActivate();
244 // move packages to deactivate back to packages directory
245 _RevertRemovePackagesToDeactivate();
247 // revert user and group changes
248 _RevertUserGroupChanges();
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();
256 VolumeState*
257 CommitTransactionHandler::DetachVolumeState()
259 VolumeState* result = fVolumeState;
260 fVolumeState = NULL;
261 return result;
265 void
266 CommitTransactionHandler::_GetPackagesToDeactivate(
267 const BActivationTransaction& transaction)
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;
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);
284 fPackagesToDeactivate.insert(package);
289 void
290 CommitTransactionHandler::_ReadPackagesToActivate(
291 const BActivationTransaction& transaction)
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;
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);
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));
324 } else
325 ERROR("Failed to open transaction directory: %s\n", strerror(error));
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);
336 // read the packages
337 for (int32 i = 0; i < packagesToActivateCount; i++) {
338 BString packageName = packagesToActivate.StringAt(i);
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;
350 if (fPackagesToDeactivate.find(package)
351 == fPackagesToDeactivate.end()) {
352 throw Exception(B_TRANSACTION_PACKAGE_ALREADY_EXISTS)
353 .SetPackageName(packageName);
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);
374 if (!fPackagesToActivate.AddItem(package)) {
375 delete package;
376 throw Exception(B_TRANSACTION_NO_MEMORY);
382 void
383 CommitTransactionHandler::_ApplyChanges()
385 // create an old state directory
386 _CreateOldStateDirectory();
388 // move packages to deactivate to old state directory
389 _RemovePackagesToDeactivate();
391 // move packages to activate to packages directory
392 _AddPackagesToActivate();
394 // activate/deactivate packages
395 _ChangePackageActivation(fAddedPackages, fRemovedPackages);
397 if (fVolumeStateIsActive) {
398 // run post-installation scripts
399 _RunPostInstallScripts();
400 } else {
401 _QueuePostInstallScripts();
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();
412 void
413 CommitTransactionHandler::_CreateOldStateDirectory()
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";
426 if (baseName.IsEmpty())
427 throw Exception(B_TRANSACTION_NO_MEMORY);
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);
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);
451 // create the directory
452 FSTransaction::CreateOperation createOldStateDirectoryOperation(
453 &fFSTransaction, FSUtils::Entry(adminDirectory, directoryName));
455 error = adminDirectory.CreateDirectory(directoryName,
456 &fOldStateDirectory);
457 if (error == B_OK) {
458 createOldStateDirectoryOperation.Finished();
460 fOldStateDirectoryName = directoryName;
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));
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);
476 // write the old activation file
477 BEntry activationFile;
478 _WriteActivationFile(RelativePath(kAdminDirectoryName, directoryName),
479 kActivationFileName, PackageSet(), PackageSet(), activationFile);
481 fResult.SetOldStateDirectory(fOldStateDirectoryName);
485 void
486 CommitTransactionHandler::_RemovePackagesToDeactivate()
488 if (fPackagesToDeactivate.empty())
489 return;
491 for (PackageSet::const_iterator it = fPackagesToDeactivate.begin();
492 it != fPackagesToDeactivate.end(); ++it) {
493 Package* package = *it;
495 // When deactivating (or updating) a system package, don't do that live.
496 if (_IsSystemPackage(package))
497 fVolumeStateIsActive = false;
499 if (fPackagesAlreadyRemoved.find(package)
500 != fPackagesAlreadyRemoved.end()) {
501 fRemovedPackages.insert(package);
502 continue;
505 // get a BEntry for the package
506 NotOwningEntryRef entryRef(package->EntryRef());
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);
519 // move entry
520 fRemovedPackages.insert(package);
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);
536 fPackageFileManager->PackageFileMoved(package->File(),
537 fOldStateDirectoryRef);
538 package->File()->IncrementEntryRemovedIgnoreLevel();
543 void
544 CommitTransactionHandler::_AddPackagesToActivate()
546 if (fPackagesToActivate.IsEmpty())
547 return;
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);
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;
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);
584 // move entry
585 fAddedPackages.insert(package);
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);
601 fPackageFileManager->PackageFileMoved(package->File(),
602 fVolume->PackagesDirectoryRef());
603 package->File()->IncrementEntryCreatedIgnoreLevel();
605 // also add the package to the volume
606 fVolumeState->AddPackage(package);
608 _PreparePackageToActivate(package);
613 void
614 CommitTransactionHandler::_PreparePackageToActivate(Package* package)
616 fCurrentPackage = package;
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));
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);
629 // handle global writable files
630 _AddGlobalWritableFiles(package);
632 fCurrentPackage = NULL;
636 void
637 CommitTransactionHandler::_AddGroup(Package* package, const BString& groupName)
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;
648 // add it
649 fAddedGroups.insert(groupName.String());
651 std::string commandLine("groupadd ");
652 commandLine += FSUtils::ShellEscapeString(groupName).String();
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);
664 void
665 CommitTransactionHandler::_AddUser(Package* package, const BUser& user)
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;
676 // add it
677 fAddedUsers.insert(user.Name().String());
679 std::string commandLine("useradd ");
681 if (!user.RealName().IsEmpty()) {
682 commandLine += std::string("-n ")
683 + FSUtils::ShellEscapeString(user.RealName()).String() + " ";
686 if (!user.Home().IsEmpty()) {
687 commandLine += std::string("-d ")
688 + FSUtils::ShellEscapeString(user.Home()).String() + " ";
691 if (!user.Shell().IsEmpty()) {
692 commandLine += std::string("-s ")
693 + FSUtils::ShellEscapeString(user.Shell()).String() + " ";
696 if (!user.Groups().IsEmpty()) {
697 commandLine += std::string("-g ")
698 + FSUtils::ShellEscapeString(user.Groups().First()).String()
699 + " ";
702 commandLine += FSUtils::ShellEscapeString(user.Name()).String();
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());
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));
734 void
735 CommitTransactionHandler::_AddGlobalWritableFiles(Package* package)
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();
747 if (contentPaths.IsEmpty())
748 return;
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);
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);
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);
780 // extract files into a subdir of the writable-files directory
781 BDirectory extractedFilesDirectory;
782 _ExtractPackageContent(package, contentPaths,
783 fWritableFilesDirectory, extractedFilesDirectory);
785 for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i);
786 i++) {
787 if (file->IsIncluded()) {
788 _AddGlobalWritableFile(package, *file, rootDirectory,
789 extractedFilesDirectory);
795 void
796 CommitTransactionHandler::_AddGlobalWritableFile(Package* package,
797 const BGlobalWritableFileInfo& file, const BDirectory& rootDirectory,
798 const BDirectory& extractedFilesDirectory)
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();
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();
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);
833 } else {
834 sourceDirectory = &extractedFilesDirectory;
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();
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);
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());
868 void
869 CommitTransactionHandler::_AddGlobalWritableFileRecurse(Package* package,
870 const BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath,
871 const BDirectory& targetDirectory, const char* targetName,
872 BWritableFileUpdateType updateType)
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.
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();
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);
907 copyOperation.Finished();
908 return;
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);
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;
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);
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);
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();
973 PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
974 "writable directory, recursion done\n");
975 return;
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)));
991 return;
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;
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();
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)));
1038 return;
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)));
1075 return;
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)));
1110 return;
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();
1123 // copy
1124 FSTransaction::CreateOperation copyOperation(&fFSTransaction,
1125 FSUtils::Entry(targetDirectory, tempTargetName));
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);
1142 copyOperation.Finished();
1144 // rename
1145 FSTransaction::RemoveOperation renameOperation(&fFSTransaction,
1146 FSUtils::Entry(targetDirectory, targetName),
1147 FSUtils::Entry(fWritableFilesDirectory,
1148 originalRelativeSourcePath));
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);
1163 renameOperation.Finished();
1164 copyOperation.Unregister();
1168 void
1169 CommitTransactionHandler::_RevertAddPackagesToActivate()
1171 if (fAddedPackages.empty())
1172 return;
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));
1182 for (PackageSet::iterator it = fAddedPackages.begin();
1183 it != fAddedPackages.end(); ++it) {
1184 // remove package from the volume
1185 Package* package = *it;
1187 if (fPackagesAlreadyAdded.find(package)
1188 != fPackagesAlreadyAdded.end()) {
1189 continue;
1192 fVolumeState->RemovePackage(package);
1194 if (transactionDirectory.InitCheck() != B_OK)
1195 continue;
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;
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;
1216 fPackageFileManager->PackageFileMoved(package->File(),
1217 fTransactionDirectoryRef);
1218 package->File()->IncrementEntryRemovedIgnoreLevel();
1223 void
1224 CommitTransactionHandler::_RevertRemovePackagesToDeactivate()
1226 if (fRemovedPackages.empty())
1227 return;
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);
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;
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;
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;
1266 fPackageFileManager->PackageFileMoved(package->File(),
1267 fVolume->PackagesDirectoryRef());
1268 package->File()->IncrementEntryCreatedIgnoreLevel();
1273 void
1274 CommitTransactionHandler::_RevertUserGroupChanges()
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());
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());
1296 void
1297 CommitTransactionHandler::_RunPostInstallScripts()
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));
1309 fCurrentPackage = NULL;
1313 void
1314 CommitTransactionHandler::_RunPostInstallScript(Package* package,
1315 const BString& script)
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;
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));
1355 void
1356 CommitTransactionHandler::_QueuePostInstallScripts()
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;
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;
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;
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;
1401 void
1402 CommitTransactionHandler::_ExtractPackageContent(Package* package,
1403 const BStringList& contentPaths, BDirectory& targetDirectory,
1404 BDirectory& _extractedFilesDirectory)
1406 // check whether the subdirectory already exists
1407 BString targetName(package->RevisionedNameThrows());
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);
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);
1432 return;
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();
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);
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);
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);
1480 createSubDirectoryOperation.Finished();
1482 // extract
1483 NotOwningEntryRef packageRef(package->EntryRef());
1485 int32 contentPathCount = contentPaths.CountStrings();
1486 for (int32 i = 0; i < contentPathCount; i++) {
1487 const char* contentPath = contentPaths.StringAt(i);
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);
1499 // tag all entries with the package attribute
1500 _TagPackageEntriesRecursively(subDirectory, targetName, true);
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);
1514 // keep the directory, regardless of whether the transaction is rolled
1515 // back
1516 createSubDirectoryOperation.Unregister();
1520 status_t
1521 CommitTransactionHandler::_OpenPackagesSubDirectory(const RelativePath& path,
1522 bool create, BDirectory& _directory)
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);
1533 return FSUtils::OpenSubDirectory(directory, path, create, _directory);
1537 status_t
1538 CommitTransactionHandler::_OpenPackagesFile(
1539 const RelativePath& subDirectoryPath, const char* fileName, uint32 openMode,
1540 BFile& _file, BEntry* _entry)
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);
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);
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);
1570 return _file.SetTo(&entry, openMode);
1574 void
1575 CommitTransactionHandler::_WriteActivationFile(
1576 const RelativePath& directoryPath, const char* fileName,
1577 const PackageSet& toActivate, const PackageSet& toDeactivate,
1578 BEntry& _entry)
1580 // create the content
1581 BString activationFileContent;
1582 _CreateActivationFileContent(toActivate, toDeactivate,
1583 activationFileContent);
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);
1599 void
1600 CommitTransactionHandler::_CreateActivationFileContent(
1601 const PackageSet& toActivate, const PackageSet& toDeactivate,
1602 BString& _content)
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);
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);
1630 _content = activationFileContent;
1634 status_t
1635 CommitTransactionHandler::_WriteTextFile(const RelativePath& directoryPath,
1636 const char* fileName, const BString& content, BEntry& _entry)
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;
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;
1657 return B_OK;
1661 void
1662 CommitTransactionHandler::_ChangePackageActivation(
1663 const PackageSet& packagesToActivate,
1664 const PackageSet& packagesToDeactivate)
1666 INFORM("CommitTransactionHandler::_ChangePackageActivation(): activating "
1667 "%zu, deactivating %zu packages\n", packagesToActivate.size(),
1668 packagesToDeactivate.size());
1670 // write the temporary package activation file
1671 BEntry activationFileEntry;
1672 _WriteActivationFile(RelativePath(kAdminDirectoryName),
1673 kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate,
1674 activationFileEntry);
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!
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);
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.
1703 // Update our state, i.e. remove deactivated packages and mark activated
1704 // packages accordingly.
1705 fVolumeState->ActivationChanged(packagesToActivate, packagesToDeactivate);
1709 void
1710 CommitTransactionHandler::_ChangePackageActivationIOCtl(
1711 const PackageSet& packagesToActivate,
1712 const PackageSet& packagesToDeactivate)
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);
1720 for (PackageSet::iterator it = packagesToActivate.begin();
1721 it != packagesToActivate.end(); ++it) {
1722 requestSize += (*it)->FileName().Length() + 1;
1725 for (PackageSet::iterator it = packagesToDeactivate.begin();
1726 it != packagesToDeactivate.end(); ++it) {
1727 requestSize += (*it)->FileName().Length() + 1;
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);
1737 request->itemCount = itemCount;
1739 PackageFSActivationChangeItem* item = &request->items[0];
1740 char* nameBuffer = (char*)(item + itemCount);
1742 for (PackageSet::iterator it = packagesToActivate.begin();
1743 it != packagesToActivate.end(); ++it, item++) {
1744 _FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it,
1745 nameBuffer);
1748 for (PackageSet::iterator it = packagesToDeactivate.begin();
1749 it != packagesToDeactivate.end(); ++it, item++) {
1750 _FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it,
1751 nameBuffer);
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);
1763 FileDescriptorCloser fdCloser(fd);
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);
1774 void
1775 CommitTransactionHandler::_FillInActivationChangeItem(
1776 PackageFSActivationChangeItem* item, PackageFSActivationChangeType type,
1777 Package* package, char*& nameBuffer)
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;
1791 bool
1792 CommitTransactionHandler::_IsSystemPackage(Package* package)
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;
1803 BPackageArchitecture architecture;
1804 return BPackageInfo::GetArchitectureByName(name.String() + 6, architecture)
1805 == B_OK;
1809 void
1810 CommitTransactionHandler::_AddIssue(const TransactionIssueBuilder& builder)
1812 fResult.AddIssue(builder.BuildIssue(fCurrentPackage));
1816 /*static*/ BString
1817 CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry,
1818 const BString& fallback)
1820 BString path = entry.Path();
1821 return path.IsEmpty() ? fallback : path;
1825 /*static*/ void
1826 CommitTransactionHandler::_TagPackageEntriesRecursively(BDirectory& directory,
1827 const BString& value, bool nonDirectoriesOnly)
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;
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);
1847 bool isDirectory = S_ISDIR(st.st_mode);
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);
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);
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);
1882 // recurse
1883 if (isDirectory) {
1884 _TagPackageEntriesRecursively(stackDirectory, value,
1885 nonDirectoriesOnly);