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