xref: /haiku/src/servers/package/CommitTransactionHandler.cpp (revision 9f3bdf3d039430b5172c424def20ce5d9f7367d4)
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 	time_t stateTime = 0;
456 	{
457 		// use the modification time of the old activations file, if possible
458 		BFile oldActivationFile;
459 		BEntry oldActivationEntry;
460 		if (_OpenPackagesFile(RelativePath(kAdminDirectoryName), kActivationFileName,
461 				B_READ_ONLY, oldActivationFile, &oldActivationEntry) != B_OK
462 					|| oldActivationEntry.GetModificationTime(&stateTime) != B_OK) {
463 			stateTime = time(NULL);
464 		}
465 	}
466 
467 	// construct a nice name from the date and time
468 	struct tm now;
469 	BString baseName;
470 	if (localtime_r(&stateTime, &now) != NULL) {
471 		baseName.SetToFormat("state_%d-%02d-%02d_%02d:%02d:%02d",
472 			1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour,
473 			now.tm_min, now.tm_sec);
474 	} else
475 		baseName = "state";
476 
477 	if (baseName.IsEmpty())
478 		throw Exception(B_TRANSACTION_NO_MEMORY);
479 
480 	// make sure the directory doesn't exist yet
481 	BDirectory adminDirectory;
482 	status_t error = _OpenPackagesSubDirectory(
483 		RelativePath(kAdminDirectoryName), true, adminDirectory);
484 	if (error != B_OK) {
485 		ERROR("Failed to open administrative directory: %s\n", strerror(error));
486 		throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
487 			.SetPath1(_GetPath(
488 				FSUtils::Entry(fVolume->PackagesDirectoryRef(),
489 					kAdminDirectoryName),
490 				kAdminDirectoryName))
491 			.SetSystemError(error);
492 	}
493 
494 	int uniqueId = 1;
495 	BString directoryName = baseName;
496 	while (BEntry(&adminDirectory, directoryName).Exists()) {
497 		directoryName.SetToFormat("%s-%d", baseName.String(), uniqueId++);
498 		if (directoryName.IsEmpty())
499 			throw Exception(B_TRANSACTION_NO_MEMORY);
500 	}
501 
502 	// create the directory
503 	FSTransaction::CreateOperation createOldStateDirectoryOperation(
504 		&fFSTransaction, FSUtils::Entry(adminDirectory, directoryName));
505 
506 	error = adminDirectory.CreateDirectory(directoryName,
507 		&fOldStateDirectory);
508 	if (error == B_OK) {
509 		createOldStateDirectoryOperation.Finished();
510 
511 		fOldStateDirectoryName = directoryName;
512 
513 		error = fOldStateDirectory.GetNodeRef(&fOldStateDirectoryRef);
514 		if (error != B_OK)
515 			ERROR("Failed get old state directory ref: %s\n", strerror(error));
516 	} else
517 		ERROR("Failed to create old state directory: %s\n", strerror(error));
518 
519 	if (error != B_OK) {
520 		throw Exception(B_TRANSACTION_FAILED_TO_CREATE_DIRECTORY)
521 			.SetPath1(_GetPath(
522 				FSUtils::Entry(adminDirectory, directoryName),
523 				directoryName))
524 			.SetSystemError(error);
525 	}
526 
527 	// write the old activation file
528 	BEntry activationFile;
529 	_WriteActivationFile(RelativePath(kAdminDirectoryName, directoryName),
530 		kActivationFileName, PackageSet(), PackageSet(), activationFile);
531 
532 	fResult.SetOldStateDirectory(fOldStateDirectoryName);
533 }
534 
535 
536 void
537 CommitTransactionHandler::_RemovePackagesToDeactivate()
538 {
539 	if (fPackagesToDeactivate.empty())
540 		return;
541 
542 	for (PackageSet::const_iterator it = fPackagesToDeactivate.begin();
543 		it != fPackagesToDeactivate.end(); ++it) {
544 		Package* package = *it;
545 
546 		// When deactivating (or updating) a system package, don't do that live.
547 		if (_IsSystemPackage(package))
548 			fVolumeStateIsActive = false;
549 
550 		if (fPackagesAlreadyRemoved.find(package)
551 				!= fPackagesAlreadyRemoved.end()) {
552 			fRemovedPackages.insert(package);
553 			continue;
554 		}
555 
556 		// get a BEntry for the package
557 		NotOwningEntryRef entryRef(package->EntryRef());
558 
559 		BEntry entry;
560 		status_t error = entry.SetTo(&entryRef);
561 		if (error != B_OK) {
562 			ERROR("Failed to get package entry for %s: %s\n",
563 				package->FileName().String(), strerror(error));
564 			throw Exception(B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH)
565 				.SetPath1(package->FileName())
566 				.SetPackageName(package->FileName())
567 				.SetSystemError(error);
568 		}
569 
570 		// move entry
571 		fRemovedPackages.insert(package);
572 
573 		error = entry.MoveTo(&fOldStateDirectory);
574 		if (error != B_OK) {
575 			fRemovedPackages.erase(package);
576 			ERROR("Failed to move old package %s from packages directory: %s\n",
577 				package->FileName().String(), strerror(error));
578 			throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE)
579 				.SetPath1(
580 					_GetPath(FSUtils::Entry(entryRef), package->FileName()))
581 				.SetPath2(_GetPath(
582 					FSUtils::Entry(fOldStateDirectory),
583 					fOldStateDirectoryName))
584 				.SetSystemError(error);
585 		}
586 
587 		fPackageFileManager->PackageFileMoved(package->File(),
588 			fOldStateDirectoryRef);
589 		package->File()->IncrementEntryRemovedIgnoreLevel();
590 	}
591 }
592 
593 
594 void
595 CommitTransactionHandler::_AddPackagesToActivate()
596 {
597 	if (fPackagesToActivate.IsEmpty())
598 		return;
599 
600 	// open packages directory
601 	BDirectory packagesDirectory;
602 	status_t error
603 		= packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef());
604 	if (error != B_OK) {
605 		ERROR("Failed to open packages directory: %s\n", strerror(error));
606 		throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
607 			.SetPath1("<packages>")
608 			.SetSystemError(error);
609 	}
610 
611 	int32 count = fPackagesToActivate.CountItems();
612 	for (int32 i = 0; i < count; i++) {
613 		Package* package = fPackagesToActivate.ItemAt(i);
614 		if (fPackagesAlreadyAdded.find(package)
615 				!= fPackagesAlreadyAdded.end()) {
616 			fAddedPackages.insert(package);
617 			_PreparePackageToActivate(package);
618 			continue;
619 		}
620 
621 		// get a BEntry for the package
622 		NotOwningEntryRef entryRef(fTransactionDirectoryRef,
623 			package->FileName());
624 		BEntry entry;
625 		error = entry.SetTo(&entryRef);
626 		if (error != B_OK) {
627 			ERROR("Failed to get package entry for %s: %s\n",
628 				package->FileName().String(), strerror(error));
629 			throw Exception(B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH)
630 				.SetPath1(package->FileName())
631 				.SetPackageName(package->FileName())
632 				.SetSystemError(error);
633 		}
634 
635 		// move entry
636 		fAddedPackages.insert(package);
637 
638 		error = entry.MoveTo(&packagesDirectory);
639 		if (error == B_FILE_EXISTS) {
640 			error = _AssertEntriesAreEqual(entry, &packagesDirectory);
641 			if (error == B_OK) {
642 				// Packages are identical, no need to move.
643 				// If the entry is not removed however, it will prevent
644 				// the transaction directory from being removed later.
645 				// We ignore failure to Remove() here, though.
646 				entry.Remove();
647 			} else if (error != B_FILE_EXISTS) {
648 				ERROR("Failed to compare new package %s to existing file in "
649 					"packages directory: %s\n", package->FileName().String(),
650 					strerror(error));
651 				// Restore original error to avoid confusion
652 				error = B_FILE_EXISTS;
653 			}
654 		}
655 		if (error != B_OK) {
656 			fAddedPackages.erase(package);
657 			ERROR("Failed to move new package %s to packages directory: %s\n",
658 				package->FileName().String(), strerror(error));
659 			throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE)
660 				.SetPath1(
661 					_GetPath(FSUtils::Entry(entryRef), package->FileName()))
662 				.SetPath2(_GetPath(
663 					FSUtils::Entry(packagesDirectory),
664 					"packages"))
665 				.SetSystemError(error);
666 		}
667 
668 		fPackageFileManager->PackageFileMoved(package->File(),
669 			fVolume->PackagesDirectoryRef());
670 		package->File()->IncrementEntryCreatedIgnoreLevel();
671 
672 		// also add the package to the volume
673 		fVolumeState->AddPackage(package);
674 
675 		_PreparePackageToActivate(package);
676 	}
677 }
678 
679 
680 void
681 CommitTransactionHandler::_PrepareFirstBootPackages()
682 {
683 	int32 count = fPackagesToActivate.CountItems();
684 
685 	BDirectory transactionDir(&fTransactionDirectoryRef);
686 	BEntry transactionEntry;
687 	BPath transactionPath;
688 	if (transactionDir.InitCheck() == B_OK &&
689 			transactionDir.GetEntry(&transactionEntry) == B_OK &&
690 			transactionEntry.GetPath(&transactionPath) == B_OK) {
691 		INFORM("Starting First Boot Processing for %d packages in %s.\n",
692 			(int) count, transactionPath.Path());
693 	}
694 
695 	for (int32 i = 0; i < count; i++) {
696 		Package* package = fPackagesToActivate.ItemAt(i);
697 		fAddedPackages.insert(package);
698 		INFORM("Doing first boot processing #%d for package %s.\n",
699 			(int) i, package->FileName().String());
700 		_PreparePackageToActivate(package);
701 	}
702 }
703 
704 
705 void
706 CommitTransactionHandler::_PreparePackageToActivate(Package* package)
707 {
708 	fCurrentPackage = package;
709 
710 	// add groups
711 	const BStringList& groups = package->Info().Groups();
712 	int32 count = groups.CountStrings();
713 	for (int32 i = 0; i < count; i++)
714 		_AddGroup(package, groups.StringAt(i));
715 
716 	// add users
717 	const BObjectList<BUser>& users = package->Info().Users();
718 	for (int32 i = 0; const BUser* user = users.ItemAt(i); i++)
719 		_AddUser(package, *user);
720 
721 	// handle global writable files
722 	_AddGlobalWritableFiles(package);
723 
724 	fCurrentPackage = NULL;
725 }
726 
727 
728 void
729 CommitTransactionHandler::_AddGroup(Package* package, const BString& groupName)
730 {
731 	// Check whether the group already exists.
732 	char buffer[256];
733 	struct group groupBuffer;
734 	struct group* groupFound;
735 	int error = getgrnam_r(groupName, &groupBuffer, buffer, sizeof(buffer),
736 		&groupFound);
737 	if ((error == 0 && groupFound != NULL) || error == ERANGE)
738 		return;
739 
740 	// add it
741 	fAddedGroups.insert(groupName.String());
742 
743 	std::string commandLine("groupadd ");
744 	commandLine += FSUtils::ShellEscapeString(groupName).String();
745 
746 	if (system(commandLine.c_str()) != 0) {
747 		fAddedGroups.erase(groupName.String());
748 		ERROR("Failed to add group \"%s\".\n", groupName.String());
749 		throw Exception(B_TRANSACTION_FAILED_TO_ADD_GROUP)
750 			.SetPackageName(package->FileName())
751 			.SetString1(groupName);
752 	}
753 }
754 
755 
756 void
757 CommitTransactionHandler::_AddUser(Package* package, const BUser& user)
758 {
759 	// Check whether the user already exists.
760 	char buffer[256];
761 	struct passwd passwdBuffer;
762 	struct passwd* passwdFound;
763 	int error = getpwnam_r(user.Name(), &passwdBuffer, buffer,
764 		sizeof(buffer), &passwdFound);
765 	if ((error == 0 && passwdFound != NULL) || error == ERANGE)
766 		return;
767 
768 	// add it
769 	fAddedUsers.insert(user.Name().String());
770 
771 	std::string commandLine("useradd ");
772 
773 	if (!user.RealName().IsEmpty()) {
774 		commandLine += std::string("-n ")
775 			+ FSUtils::ShellEscapeString(user.RealName()).String() + " ";
776 	}
777 
778 	if (!user.Home().IsEmpty()) {
779 		commandLine += std::string("-d ")
780 			+ FSUtils::ShellEscapeString(user.Home()).String() + " ";
781 	}
782 
783 	if (!user.Shell().IsEmpty()) {
784 		commandLine += std::string("-s ")
785 			+ FSUtils::ShellEscapeString(user.Shell()).String() + " ";
786 	}
787 
788 	if (!user.Groups().IsEmpty()) {
789 		commandLine += std::string("-g ")
790 			+ FSUtils::ShellEscapeString(user.Groups().First()).String()
791 			+ " ";
792 	}
793 
794 	commandLine += FSUtils::ShellEscapeString(user.Name()).String();
795 
796 	if (system(commandLine.c_str()) != 0) {
797 		fAddedUsers.erase(user.Name().String());
798 		ERROR("Failed to add user \"%s\".\n", user.Name().String());
799 		throw Exception(B_TRANSACTION_FAILED_TO_ADD_USER)
800 			.SetPackageName(package->FileName())
801 			.SetString1(user.Name());
802 
803 	}
804 
805 	// add the supplementary groups
806 	int32 groupCount = user.Groups().CountStrings();
807 	for (int32 i = 1; i < groupCount; i++) {
808 		commandLine = std::string("groupmod -A ")
809 			+ FSUtils::ShellEscapeString(user.Name()).String()
810 			+ " "
811 			+ FSUtils::ShellEscapeString(user.Groups().StringAt(i))
812 				.String();
813 		if (system(commandLine.c_str()) != 0) {
814 			fAddedUsers.erase(user.Name().String());
815 			ERROR("Failed to add user \"%s\" to group \"%s\".\n",
816 				user.Name().String(), user.Groups().StringAt(i).String());
817 			throw Exception(B_TRANSACTION_FAILED_TO_ADD_USER_TO_GROUP)
818 				.SetPackageName(package->FileName())
819 				.SetString1(user.Name())
820 				.SetString2(user.Groups().StringAt(i));
821 		}
822 	}
823 }
824 
825 
826 void
827 CommitTransactionHandler::_AddGlobalWritableFiles(Package* package)
828 {
829 	// get the list of included files
830 	const BObjectList<BGlobalWritableFileInfo>& files
831 		= package->Info().GlobalWritableFileInfos();
832 	BStringList contentPaths;
833 	for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i);
834 		i++) {
835 		if (file->IsIncluded() && !contentPaths.Add(file->Path()))
836 			throw std::bad_alloc();
837 	}
838 
839 	if (contentPaths.IsEmpty())
840 		return;
841 
842 	// Open the root directory of the installation location where we will
843 	// extract the files -- that's the volume's root directory.
844 	BDirectory rootDirectory;
845 	status_t error = rootDirectory.SetTo(&fVolume->RootDirectoryRef());
846 	if (error != B_OK) {
847 		throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
848 			.SetPath1(_GetPath(
849 				FSUtils::Entry(fVolume->RootDirectoryRef()),
850 				"<packagefs root>"))
851 			.SetSystemError(error);
852 	}
853 
854 	// Open writable-files directory in the administrative directory.
855 	if (fWritableFilesDirectory.InitCheck() != B_OK) {
856 		RelativePath directoryPath(kAdminDirectoryName,
857 			kWritableFilesDirectoryName);
858 		error = _OpenPackagesSubDirectory(directoryPath, true,
859 			fWritableFilesDirectory);
860 
861 		if (error != B_OK) {
862 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
863 				.SetPath1(_GetPath(
864 					FSUtils::Entry(fVolume->PackagesDirectoryRef(),
865 						directoryPath.ToString()),
866 					directoryPath.ToString()))
867 				.SetPackageName(package->FileName())
868 				.SetSystemError(error);
869 		}
870 	}
871 
872 	// extract files into a subdir of the writable-files directory
873 	BDirectory extractedFilesDirectory;
874 	_ExtractPackageContent(package, contentPaths,
875 		fWritableFilesDirectory, extractedFilesDirectory);
876 
877 	for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i);
878 		i++) {
879 		if (file->IsIncluded()) {
880 			_AddGlobalWritableFile(package, *file, rootDirectory,
881 				extractedFilesDirectory);
882 		}
883 	}
884 }
885 
886 
887 void
888 CommitTransactionHandler::_AddGlobalWritableFile(Package* package,
889 	const BGlobalWritableFileInfo& file, const BDirectory& rootDirectory,
890 	const BDirectory& extractedFilesDirectory)
891 {
892 	// open parent directory of the source entry
893 	const char* lastSlash = strrchr(file.Path(), '/');
894 	const BDirectory* sourceDirectory;
895 	BDirectory stackSourceDirectory;
896 	if (lastSlash != NULL) {
897 		sourceDirectory = &stackSourceDirectory;
898 		BString sourceParentPath(file.Path(),
899 			lastSlash - file.Path().String());
900 		if (sourceParentPath.Length() == 0)
901 			throw std::bad_alloc();
902 
903 		status_t error = stackSourceDirectory.SetTo(
904 			&extractedFilesDirectory, sourceParentPath);
905 		if (error != B_OK) {
906 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
907 				.SetPath1(_GetPath(
908 					FSUtils::Entry(extractedFilesDirectory, sourceParentPath),
909 					sourceParentPath))
910 				.SetPackageName(package->FileName())
911 				.SetSystemError(error);
912 		}
913 	} else {
914 		sourceDirectory = &extractedFilesDirectory;
915 	}
916 
917 	// open parent directory of the target entry -- create, if necessary
918 	BString targetPath(file.Path());
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 	FileDescriptorCloser fd(fVolume->OpenRootDirectory());
1867 	if (!fd.IsSet()) {
1868 		throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
1869 			.SetPath1(_GetPath(
1870 				FSUtils::Entry(fVolume->RootDirectoryRef()),
1871 				"<packagefs root>"))
1872 			.SetSystemError(fd.Get());
1873 	}
1874 
1875 	if (ioctl(fd.Get(), PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request,
1876 		requestSize) != 0) {
1877 // TODO: We need more error information and error handling!
1878 		throw Exception(B_TRANSACTION_FAILED_TO_CHANGE_PACKAGE_ACTIVATION)
1879 			.SetSystemError(errno);
1880 	}
1881 }
1882 
1883 
1884 void
1885 CommitTransactionHandler::_FillInActivationChangeItem(
1886 	PackageFSActivationChangeItem* item, PackageFSActivationChangeType type,
1887 	Package* package, char*& nameBuffer)
1888 {
1889 	item->type = type;
1890 	item->packageDeviceID = package->NodeRef().device;
1891 	item->packageNodeID = package->NodeRef().node;
1892 	item->nameLength = package->FileName().Length();
1893 	item->parentDeviceID = fVolume->PackagesDeviceID();
1894 	item->parentDirectoryID = fVolume->PackagesDirectoryID();
1895 	item->name = nameBuffer;
1896 	strcpy(nameBuffer, package->FileName());
1897 	nameBuffer += package->FileName().Length() + 1;
1898 }
1899 
1900 
1901 bool
1902 CommitTransactionHandler::_IsSystemPackage(Package* package)
1903 {
1904 	// package name should be "haiku[_<arch>]"
1905 	const BString& name = package->Info().Name();
1906 	if (!name.StartsWith("haiku"))
1907 		return false;
1908 	if (name.Length() == 5)
1909 		return true;
1910 	if (name[5] != '_')
1911 		return false;
1912 
1913 	BPackageArchitecture architecture;
1914 	return BPackageInfo::GetArchitectureByName(name.String() + 6, architecture)
1915 		== B_OK;
1916 }
1917 
1918 
1919 void
1920 CommitTransactionHandler::_AddIssue(const TransactionIssueBuilder& builder)
1921 {
1922 	fResult.AddIssue(builder.BuildIssue(fCurrentPackage));
1923 }
1924 
1925 
1926 /*static*/ BString
1927 CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry,
1928 	const BString& fallback)
1929 {
1930 	BString path = entry.Path();
1931 	return path.IsEmpty() ? fallback : path;
1932 }
1933 
1934 
1935 /*static*/ void
1936 CommitTransactionHandler::_TagPackageEntriesRecursively(BDirectory& directory,
1937 	const BString& value, bool nonDirectoriesOnly)
1938 {
1939 	char buffer[offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH];
1940 	dirent *entry = (dirent*)buffer;
1941 	while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
1942 		if (strcmp(entry->d_name, ".") == 0
1943 			|| strcmp(entry->d_name, "..") == 0) {
1944 			continue;
1945 		}
1946 
1947 		// determine type
1948 		struct stat st;
1949 		status_t error = directory.GetStatFor(entry->d_name, &st);
1950 		if (error != B_OK) {
1951 			throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY)
1952 				.SetPath1(_GetPath(
1953 					FSUtils::Entry(directory, entry->d_name),
1954 					entry->d_name))
1955 				.SetSystemError(error);
1956 		}
1957 		bool isDirectory = S_ISDIR(st.st_mode);
1958 
1959 		// open the node and set the attribute
1960 		BNode stackNode;
1961 		BDirectory stackDirectory;
1962 		BNode* node;
1963 		if (isDirectory) {
1964 			node = &stackDirectory;
1965 			error = stackDirectory.SetTo(&directory, entry->d_name);
1966 		} else {
1967 			node = &stackNode;
1968 			error = stackNode.SetTo(&directory, entry->d_name);
1969 		}
1970 
1971 		if (error != B_OK) {
1972 			throw Exception(isDirectory
1973 					? B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY
1974 					: B_TRANSACTION_FAILED_TO_OPEN_FILE)
1975 				.SetPath1(_GetPath(
1976 					FSUtils::Entry(directory, entry->d_name),
1977 					entry->d_name))
1978 				.SetSystemError(error);
1979 		}
1980 
1981 		if (!isDirectory || !nonDirectoriesOnly) {
1982 			error = node->WriteAttrString(kPackageFileAttribute, &value);
1983 			if (error != B_OK) {
1984 				throw Exception(B_TRANSACTION_FAILED_TO_WRITE_FILE_ATTRIBUTE)
1985 					.SetPath1(_GetPath(
1986 						FSUtils::Entry(directory, entry->d_name),
1987 						entry->d_name))
1988 					.SetSystemError(error);
1989 			}
1990 		}
1991 
1992 		// recurse
1993 		if (isDirectory) {
1994 			_TagPackageEntriesRecursively(stackDirectory, value,
1995 				nonDirectoriesOnly);
1996 		}
1997 	}
1998 }
1999 
2000 
2001 /*static*/ status_t
2002 CommitTransactionHandler::_AssertEntriesAreEqual(const BEntry& entry,
2003 	const BDirectory* directory)
2004 {
2005 	BFile a;
2006 	status_t status = a.SetTo(&entry, B_READ_ONLY);
2007 	if (status != B_OK)
2008 		return status;
2009 
2010 	BFile b;
2011 	status = b.SetTo(directory, entry.Name(), B_READ_ONLY);
2012 	if (status != B_OK)
2013 		return status;
2014 
2015 	off_t aSize;
2016 	status = a.GetSize(&aSize);
2017 	if (status != B_OK)
2018 		return status;
2019 
2020 	off_t bSize;
2021 	status = b.GetSize(&bSize);
2022 	if (status != B_OK)
2023 		return status;
2024 
2025 	if (aSize != bSize)
2026 		return B_FILE_EXISTS;
2027 
2028 	const size_t bufferSize = 4096;
2029 	uint8 aBuffer[bufferSize];
2030 	uint8 bBuffer[bufferSize];
2031 
2032 	while (aSize > 0) {
2033 		ssize_t aRead = a.Read(aBuffer, bufferSize);
2034 		ssize_t bRead = b.Read(bBuffer, bufferSize);
2035 		if (aRead < 0 || aRead != bRead)
2036 			return B_FILE_EXISTS;
2037 		if (memcmp(aBuffer, bBuffer, aRead) != 0)
2038 			return B_FILE_EXISTS;
2039 		aSize -= aRead;
2040 	}
2041 
2042 	INFORM("CommitTransactionHandler::_AssertEntriesAreEqual(): "
2043 		"Package file '%s' already exists in target folder "
2044 		"with equal contents\n", entry.Name());
2045 	return B_OK;
2046 }
2047