xref: /haiku/src/servers/package/CommitTransactionHandler.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
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 	// open parent directory of the source entry
882 	const char* lastSlash = strrchr(file.Path(), '/');
883 	const BDirectory* sourceDirectory;
884 	BDirectory stackSourceDirectory;
885 	if (lastSlash != NULL) {
886 		sourceDirectory = &stackSourceDirectory;
887 		BString sourceParentPath(file.Path(),
888 			lastSlash - file.Path().String());
889 		if (sourceParentPath.Length() == 0)
890 			throw std::bad_alloc();
891 
892 		status_t error = stackSourceDirectory.SetTo(
893 			&extractedFilesDirectory, sourceParentPath);
894 		if (error != B_OK) {
895 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
896 				.SetPath1(_GetPath(
897 					FSUtils::Entry(extractedFilesDirectory, sourceParentPath),
898 					sourceParentPath))
899 				.SetPackageName(package->FileName())
900 				.SetSystemError(error);
901 		}
902 	} else {
903 		sourceDirectory = &extractedFilesDirectory;
904 	}
905 
906 	// open parent directory of the target entry -- create, if necessary
907 	BString targetPath(file.Path());
908 	FSUtils::Path relativeSourcePath(file.Path());
909 	lastSlash = strrchr(targetPath, '/');
910 	if (lastSlash != NULL) {
911 		BString targetParentPath(targetPath,
912 			lastSlash - targetPath.String());
913 		if (targetParentPath.Length() == 0)
914 			throw std::bad_alloc();
915 
916 		BDirectory targetDirectory;
917 		status_t error = FSUtils::OpenSubDirectory(rootDirectory,
918 			RelativePath(targetParentPath), true, targetDirectory);
919 		if (error != B_OK) {
920 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
921 				.SetPath1(_GetPath(
922 					FSUtils::Entry(rootDirectory, targetParentPath),
923 					targetParentPath))
924 				.SetPackageName(package->FileName())
925 				.SetSystemError(error);
926 		}
927 		_AddGlobalWritableFileRecurse(package, *sourceDirectory,
928 			relativeSourcePath, targetDirectory, lastSlash + 1,
929 			file.UpdateType());
930 	} else {
931 		_AddGlobalWritableFileRecurse(package, *sourceDirectory,
932 			relativeSourcePath, rootDirectory, targetPath,
933 			file.UpdateType());
934 	}
935 }
936 
937 
938 void
939 CommitTransactionHandler::_AddGlobalWritableFileRecurse(Package* package,
940 	const BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath,
941 	const BDirectory& targetDirectory, const char* targetName,
942 	BWritableFileUpdateType updateType)
943 {
944 	// * If the file doesn't exist, just copy the extracted one.
945 	// * If the file does exist, compare with the previous original version:
946 	//   * If unchanged, just overwrite it.
947 	//   * If changed, leave it to the user for now. When we support merging
948 	//     first back the file up, then try the merge.
949 
950 	// Check whether the target location exists and what type the entry at
951 	// both locations are.
952 	struct stat targetStat;
953 	if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) {
954 		// target doesn't exist -- just copy
955 		PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
956 			"couldn't get stat for writable file \"%s\", copying...\n",
957 			targetName);
958 		FSTransaction::CreateOperation copyOperation(&fFSTransaction,
959 			FSUtils::Entry(targetDirectory, targetName));
960 		status_t error = BCopyEngine(BCopyEngine::COPY_RECURSIVELY)
961 			.CopyEntry(
962 				FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()),
963 				FSUtils::Entry(targetDirectory, targetName));
964 		if (error != B_OK) {
965 			if (targetDirectory.GetStatFor(targetName, &targetStat) == B_OK)
966 				copyOperation.Finished();
967 
968 			throw Exception(B_TRANSACTION_FAILED_TO_COPY_FILE)
969 				.SetPath1(_GetPath(
970 					FSUtils::Entry(sourceDirectory,
971 						relativeSourcePath.Leaf()),
972 					relativeSourcePath))
973 				.SetPath2(_GetPath(
974 					FSUtils::Entry(targetDirectory, targetName),
975 					targetName))
976 				.SetSystemError(error);
977 		}
978 		copyOperation.Finished();
979 		return;
980 	}
981 
982 	struct stat sourceStat;
983 	status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(),
984 		&sourceStat);
985 	if (error != B_OK) {
986 		throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY)
987 			.SetPath1(_GetPath(
988 				FSUtils::Entry(sourceDirectory,
989 					relativeSourcePath.Leaf()),
990 				relativeSourcePath))
991 			.SetSystemError(error);
992 	}
993 
994 	if ((sourceStat.st_mode & S_IFMT) != (targetStat.st_mode & S_IFMT)
995 		|| (!S_ISDIR(sourceStat.st_mode) && !S_ISREG(sourceStat.st_mode)
996 			&& !S_ISLNK(sourceStat.st_mode))) {
997 		// Source and target entry types don't match or this is an entry
998 		// we cannot handle. The user must handle this manually.
999 		PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
1000 			"writable file \"%s\" exists, but type doesn't match previous "
1001 			"type\n", targetName);
1002 		_AddIssue(TransactionIssueBuilder(
1003 				BTransactionIssue::B_WRITABLE_FILE_TYPE_MISMATCH)
1004 			.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1005 			.SetPath2(FSUtils::Entry(sourceDirectory,
1006 				relativeSourcePath.Leaf())));
1007 		return;
1008 	}
1009 
1010 	if (S_ISDIR(sourceStat.st_mode)) {
1011 		// entry is a directory -- recurse
1012 		BDirectory sourceSubDirectory;
1013 		error = sourceSubDirectory.SetTo(&sourceDirectory,
1014 			relativeSourcePath.Leaf());
1015 		if (error != B_OK) {
1016 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
1017 				.SetPath1(_GetPath(
1018 					FSUtils::Entry(sourceDirectory,
1019 						relativeSourcePath.Leaf()),
1020 					relativeSourcePath))
1021 				.SetPackageName(package->FileName())
1022 				.SetSystemError(error);
1023 		}
1024 
1025 		BDirectory targetSubDirectory;
1026 		error = targetSubDirectory.SetTo(&targetDirectory, targetName);
1027 		if (error != B_OK) {
1028 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
1029 				.SetPath1(_GetPath(
1030 					FSUtils::Entry(targetDirectory, targetName),
1031 					targetName))
1032 				.SetPackageName(package->FileName())
1033 				.SetSystemError(error);
1034 		}
1035 
1036 		entry_ref entry;
1037 		while (sourceSubDirectory.GetNextRef(&entry) == B_OK) {
1038 			relativeSourcePath.AppendComponent(entry.name);
1039 			_AddGlobalWritableFileRecurse(package, sourceSubDirectory,
1040 				relativeSourcePath, targetSubDirectory, entry.name,
1041 				updateType);
1042 			relativeSourcePath.RemoveLastComponent();
1043 		}
1044 
1045 		PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
1046 			"writable directory, recursion done\n");
1047 		return;
1048 	}
1049 
1050 	// get the package the target file originated from
1051 	BString originalPackage;
1052 	if (BNode(&targetDirectory, targetName).ReadAttrString(
1053 			kPackageFileAttribute, &originalPackage) != B_OK) {
1054 		// Can't determine the original package. The user must handle this
1055 		// manually.
1056 		PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
1057 			"failed to get SYS:PACKAGE attribute for \"%s\", can't tell if "
1058 			"file needs to be updated\n",
1059 			targetName);
1060 		if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) {
1061 			_AddIssue(TransactionIssueBuilder(
1062 					BTransactionIssue::B_WRITABLE_FILE_NO_PACKAGE_ATTRIBUTE)
1063 				.SetPath1(FSUtils::Entry(targetDirectory, targetName)));
1064 		}
1065 		return;
1066 	}
1067 
1068 	// If that's our package, we're happy.
1069 	if (originalPackage == package->RevisionedNameThrows()) {
1070 		PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
1071 			"file \"%s\" tagged with same package version we're activating\n",
1072 			targetName);
1073 		return;
1074 	}
1075 
1076 	// Check, whether the writable-files directory for the original package
1077 	// exists.
1078 	BString originalRelativeSourcePath = BString().SetToFormat("%s/%s",
1079 		originalPackage.String(), relativeSourcePath.ToCString());
1080 	if (originalRelativeSourcePath.IsEmpty())
1081 		throw std::bad_alloc();
1082 
1083 	struct stat originalPackageStat;
1084 	error = fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath,
1085 		&originalPackageStat);
1086 	if (error != B_OK
1087 		|| (sourceStat.st_mode & S_IFMT)
1088 			!= (originalPackageStat.st_mode & S_IFMT)) {
1089 		// Original entry doesn't exist (either we don't have the data from
1090 		// the original package or the entry really didn't exist) or its
1091 		// type differs from the expected one. The user must handle this
1092 		// manually.
1093 		PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
1094 			"original \"%s\" doesn't exist or has other type\n",
1095 			_GetPath(FSUtils::Entry(fWritableFilesDirectory,
1096 					originalRelativeSourcePath),
1097 				originalRelativeSourcePath).String());
1098 		if (error != B_OK) {
1099 			_AddIssue(TransactionIssueBuilder(
1100 					BTransactionIssue
1101 						::B_WRITABLE_FILE_OLD_ORIGINAL_FILE_MISSING)
1102 				.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1103 				.SetPath2(FSUtils::Entry(fWritableFilesDirectory,
1104 					originalRelativeSourcePath)));
1105 		} else {
1106 			_AddIssue(TransactionIssueBuilder(
1107 					BTransactionIssue
1108 						::B_WRITABLE_FILE_OLD_ORIGINAL_FILE_TYPE_MISMATCH)
1109 				.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1110 				.SetPath2(FSUtils::Entry(fWritableFilesDirectory,
1111 					originalRelativeSourcePath)));
1112 		}
1113 		return;
1114 	}
1115 
1116 	if (S_ISREG(sourceStat.st_mode)) {
1117 		// compare file content
1118 		bool equal;
1119 		error = FSUtils::CompareFileContent(
1120 			FSUtils::Entry(fWritableFilesDirectory,
1121 				originalRelativeSourcePath),
1122 			FSUtils::Entry(targetDirectory, targetName),
1123 			equal);
1124 		// TODO: Merge support!
1125 		if (error != B_OK || !equal) {
1126 			// The comparison failed or the files differ. The user must
1127 			// handle this manually.
1128 			PRINT("Volume::CommitTransactionHandler::"
1129 				"_AddGlobalWritableFile(): "
1130 				"file comparison \"%s\" failed (%s) or files aren't equal\n",
1131 				targetName, strerror(error));
1132 			if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) {
1133 				if (error != B_OK) {
1134 					_AddIssue(TransactionIssueBuilder(
1135 							BTransactionIssue
1136 								::B_WRITABLE_FILE_COMPARISON_FAILED)
1137 						.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1138 						.SetPath2(FSUtils::Entry(fWritableFilesDirectory,
1139 							originalRelativeSourcePath))
1140 						.SetSystemError(error));
1141 				} else {
1142 					_AddIssue(TransactionIssueBuilder(
1143 							BTransactionIssue
1144 								::B_WRITABLE_FILE_NOT_EQUAL)
1145 						.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1146 						.SetPath2(FSUtils::Entry(fWritableFilesDirectory,
1147 							originalRelativeSourcePath)));
1148 				}
1149 			}
1150 			return;
1151 		}
1152 	} else {
1153 		// compare symlinks
1154 		bool equal;
1155 		error = FSUtils::CompareSymLinks(
1156 			FSUtils::Entry(fWritableFilesDirectory,
1157 				originalRelativeSourcePath),
1158 			FSUtils::Entry(targetDirectory, targetName),
1159 			equal);
1160 		if (error != B_OK || !equal) {
1161 			// The comparison failed or the symlinks differ. The user must
1162 			// handle this manually.
1163 			PRINT("Volume::CommitTransactionHandler::"
1164 				"_AddGlobalWritableFile(): "
1165 				"symlink comparison \"%s\" failed (%s) or symlinks aren't "
1166 				"equal\n", targetName, strerror(error));
1167 			if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) {
1168 				if (error != B_OK) {
1169 					_AddIssue(TransactionIssueBuilder(
1170 							BTransactionIssue
1171 								::B_WRITABLE_SYMLINK_COMPARISON_FAILED)
1172 						.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1173 						.SetPath2(FSUtils::Entry(fWritableFilesDirectory,
1174 							originalRelativeSourcePath))
1175 						.SetSystemError(error));
1176 				} else {
1177 					_AddIssue(TransactionIssueBuilder(
1178 							BTransactionIssue
1179 								::B_WRITABLE_SYMLINK_NOT_EQUAL)
1180 						.SetPath1(FSUtils::Entry(targetDirectory, targetName))
1181 						.SetPath2(FSUtils::Entry(fWritableFilesDirectory,
1182 							originalRelativeSourcePath)));
1183 				}
1184 			}
1185 			return;
1186 		}
1187 	}
1188 
1189 	// Replace the existing file/symlink. We do that in two steps: First
1190 	// copy the new file to a neighoring location, then move-replace the
1191 	// old file.
1192 	BString tempTargetName;
1193 	tempTargetName.SetToFormat("%s.%s", targetName,
1194 		package->RevisionedNameThrows().String());
1195 	if (tempTargetName.IsEmpty())
1196 		throw std::bad_alloc();
1197 
1198 	// copy
1199 	FSTransaction::CreateOperation copyOperation(&fFSTransaction,
1200 		FSUtils::Entry(targetDirectory, tempTargetName));
1201 
1202 	error = BCopyEngine(BCopyEngine::UNLINK_DESTINATION).CopyEntry(
1203 		FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()),
1204 		FSUtils::Entry(targetDirectory, tempTargetName));
1205 	if (error != B_OK) {
1206 		throw Exception(B_TRANSACTION_FAILED_TO_COPY_FILE)
1207 			.SetPath1(_GetPath(
1208 				FSUtils::Entry(sourceDirectory,
1209 					relativeSourcePath.Leaf()),
1210 				relativeSourcePath))
1211 			.SetPath2(_GetPath(
1212 				FSUtils::Entry(targetDirectory, tempTargetName),
1213 				tempTargetName))
1214 			.SetSystemError(error);
1215 	}
1216 
1217 	copyOperation.Finished();
1218 
1219 	// rename
1220 	FSTransaction::RemoveOperation renameOperation(&fFSTransaction,
1221 		FSUtils::Entry(targetDirectory, targetName),
1222 		FSUtils::Entry(fWritableFilesDirectory,
1223 			originalRelativeSourcePath));
1224 
1225 	BEntry targetEntry;
1226 	error = targetEntry.SetTo(&targetDirectory, tempTargetName);
1227 	if (error == B_OK)
1228 		error = targetEntry.Rename(targetName, true);
1229 	if (error != B_OK) {
1230 		throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE)
1231 			.SetPath1(_GetPath(
1232 				FSUtils::Entry(targetDirectory, tempTargetName),
1233 				tempTargetName))
1234 			.SetPath2(targetName)
1235 			.SetSystemError(error);
1236 	}
1237 
1238 	renameOperation.Finished();
1239 	copyOperation.Unregister();
1240 }
1241 
1242 
1243 void
1244 CommitTransactionHandler::_RevertAddPackagesToActivate()
1245 {
1246 	if (fAddedPackages.empty() || fFirstBootProcessing)
1247 		return;
1248 
1249 	// open transaction directory
1250 	BDirectory transactionDirectory;
1251 	status_t error = transactionDirectory.SetTo(&fTransactionDirectoryRef);
1252 	if (error != B_OK) {
1253 		ERROR("failed to open transaction directory: %s\n",
1254 			strerror(error));
1255 	}
1256 
1257 	for (PackageSet::iterator it = fAddedPackages.begin();
1258 		it != fAddedPackages.end(); ++it) {
1259 		// remove package from the volume
1260 		Package* package = *it;
1261 
1262 		if (fPackagesAlreadyAdded.find(package)
1263 				!= fPackagesAlreadyAdded.end()) {
1264 			continue;
1265 		}
1266 
1267 		fVolumeState->RemovePackage(package);
1268 
1269 		if (transactionDirectory.InitCheck() != B_OK)
1270 			continue;
1271 
1272 		// get BEntry for the package
1273 		NotOwningEntryRef entryRef(package->EntryRef());
1274 		BEntry entry;
1275 		error = entry.SetTo(&entryRef);
1276 		if (error != B_OK) {
1277 			ERROR("failed to get entry for package \"%s\": %s\n",
1278 				package->FileName().String(), strerror(error));
1279 			continue;
1280 		}
1281 
1282 		// move entry
1283 		error = entry.MoveTo(&transactionDirectory);
1284 		if (error != B_OK) {
1285 			ERROR("failed to move new package \"%s\" back to transaction "
1286 				"directory: %s\n", package->FileName().String(),
1287 				strerror(error));
1288 			continue;
1289 		}
1290 
1291 		fPackageFileManager->PackageFileMoved(package->File(),
1292 			fTransactionDirectoryRef);
1293 		package->File()->IncrementEntryRemovedIgnoreLevel();
1294 	}
1295 }
1296 
1297 
1298 void
1299 CommitTransactionHandler::_RevertRemovePackagesToDeactivate()
1300 {
1301 	if (fRemovedPackages.empty() || fFirstBootProcessing)
1302 		return;
1303 
1304 	// open packages directory
1305 	BDirectory packagesDirectory;
1306 	status_t error
1307 		= packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef());
1308 	if (error != B_OK) {
1309 		throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
1310 			.SetPath1("<packages>")
1311 			.SetSystemError(error);
1312 	}
1313 
1314 	for (PackageSet::iterator it = fRemovedPackages.begin();
1315 		it != fRemovedPackages.end(); ++it) {
1316 		Package* package = *it;
1317 		if (fPackagesAlreadyRemoved.find(package)
1318 				!= fPackagesAlreadyRemoved.end()) {
1319 			continue;
1320 		}
1321 
1322 		// get a BEntry for the package
1323 		BEntry entry;
1324 		status_t error = entry.SetTo(&fOldStateDirectory,
1325 			package->FileName());
1326 		if (error != B_OK) {
1327 			ERROR("failed to get entry for package \"%s\": %s\n",
1328 				package->FileName().String(), strerror(error));
1329 			continue;
1330 		}
1331 
1332 		// move entry
1333 		error = entry.MoveTo(&packagesDirectory);
1334 		if (error != B_OK) {
1335 			ERROR("failed to move old package \"%s\" back to packages "
1336 				"directory: %s\n", package->FileName().String(),
1337 				strerror(error));
1338 			continue;
1339 		}
1340 
1341 		fPackageFileManager->PackageFileMoved(package->File(),
1342 			fVolume->PackagesDirectoryRef());
1343 		package->File()->IncrementEntryCreatedIgnoreLevel();
1344 	}
1345 }
1346 
1347 
1348 void
1349 CommitTransactionHandler::_RevertUserGroupChanges()
1350 {
1351 	// delete users
1352 	for (StringSet::const_iterator it = fAddedUsers.begin();
1353 		it != fAddedUsers.end(); ++it) {
1354 		std::string commandLine("userdel ");
1355 		commandLine += FSUtils::ShellEscapeString(it->c_str()).String();
1356 		if (system(commandLine.c_str()) != 0)
1357 			ERROR("failed to remove user \"%s\"\n", it->c_str());
1358 	}
1359 
1360 	// delete groups
1361 	for (StringSet::const_iterator it = fAddedGroups.begin();
1362 		it != fAddedGroups.end(); ++it) {
1363 		std::string commandLine("groupdel ");
1364 		commandLine += FSUtils::ShellEscapeString(it->c_str()).String();
1365 		if (system(commandLine.c_str()) != 0)
1366 			ERROR("failed to remove group \"%s\"\n", it->c_str());
1367 	}
1368 }
1369 
1370 
1371 void
1372 CommitTransactionHandler::_RunPostInstallScripts()
1373 {
1374 	for (PackageSet::iterator it = fAddedPackages.begin();
1375 		it != fAddedPackages.end(); ++it) {
1376 		Package* package = *it;
1377 		fCurrentPackage = package;
1378 		const BStringList& scripts = package->Info().PostInstallScripts();
1379 		int32 count = scripts.CountStrings();
1380 		for (int32 i = 0; i < count; i++)
1381 			_RunPostOrPreScript(package, scripts.StringAt(i), true);
1382 	}
1383 
1384 	fCurrentPackage = NULL;
1385 }
1386 
1387 
1388 void
1389 CommitTransactionHandler::_RunPreUninstallScripts()
1390 {
1391 	// Note this runs in the correct order, so dependents get uninstalled before
1392 	// the packages they depend on.  No need for a reversed loop.
1393 	for (PackageSet::iterator it = fPackagesToDeactivate.begin();
1394 		it != fPackagesToDeactivate.end(); ++it) {
1395 		Package* package = *it;
1396 		fCurrentPackage = package;
1397 		const BStringList& scripts = package->Info().PreUninstallScripts();
1398 		int32 count = scripts.CountStrings();
1399 		for (int32 i = 0; i < count; i++)
1400 			_RunPostOrPreScript(package, scripts.StringAt(i), false);
1401 	}
1402 
1403 	fCurrentPackage = NULL;
1404 }
1405 
1406 
1407 void
1408 CommitTransactionHandler::_RunPostOrPreScript(Package* package,
1409 	const BString& script, bool postNotPre)
1410 {
1411 	const char *postOrPreInstallWording = postNotPre
1412 		? "post-installation" : "pre-uninstall";
1413 	BDirectory rootDir(&fVolume->RootDirectoryRef());
1414 	BPath scriptPath(&rootDir, script);
1415 	status_t error = scriptPath.InitCheck();
1416 	if (error != B_OK) {
1417 		ERROR("Volume::CommitTransactionHandler::_RunPostOrPreScript(): "
1418 			"failed get path of %s script \"%s\" of package "
1419 			"%s: %s\n",
1420 			postOrPreInstallWording, script.String(),
1421 			package->FileName().String(), strerror(error));
1422 		_AddIssue(TransactionIssueBuilder(postNotPre
1423 				? BTransactionIssue::B_POST_INSTALL_SCRIPT_NOT_FOUND
1424 				: BTransactionIssue::B_PRE_UNINSTALL_SCRIPT_NOT_FOUND)
1425 			.SetPath1(script)
1426 			.SetSystemError(error));
1427 		return;
1428 	}
1429 
1430 	errno = 0;
1431 	int result = system(scriptPath.Path());
1432 	if (result != 0) {
1433 		ERROR("Volume::CommitTransactionHandler::_RunPostOrPreScript(): "
1434 			"running %s script \"%s\" of package %s "
1435 			"failed: %d (errno: %s)\n",
1436 			postOrPreInstallWording, script.String(),
1437 			package->FileName().String(), result, strerror(errno));
1438 		if (result < 0 || result == 127) { // bash shell returns 127 on failure.
1439 			_AddIssue(TransactionIssueBuilder(postNotPre
1440 					? BTransactionIssue::B_STARTING_POST_INSTALL_SCRIPT_FAILED
1441 					: BTransactionIssue::B_STARTING_PRE_UNINSTALL_SCRIPT_FAILED)
1442 				.SetPath1(BString(scriptPath.Path()))
1443 				.SetSystemError(errno));
1444 		} else { // positive is an exit code from the script itself.
1445 			_AddIssue(TransactionIssueBuilder(postNotPre
1446 					? BTransactionIssue::B_POST_INSTALL_SCRIPT_FAILED
1447 					: BTransactionIssue::B_PRE_UNINSTALL_SCRIPT_FAILED)
1448 				.SetPath1(BString(scriptPath.Path()))
1449 				.SetExitCode(result));
1450 		}
1451 	}
1452 }
1453 
1454 
1455 void
1456 CommitTransactionHandler::_QueuePostInstallScripts()
1457 {
1458 	BDirectory adminDirectory;
1459 	status_t error = _OpenPackagesSubDirectory(
1460 		RelativePath(kAdminDirectoryName), true, adminDirectory);
1461 	if (error != B_OK) {
1462 		ERROR("Failed to open administrative directory: %s\n", strerror(error));
1463 		return;
1464 	}
1465 
1466 	BDirectory scriptsDirectory;
1467 	error = scriptsDirectory.SetTo(&adminDirectory, kQueuedScriptsDirectoryName);
1468 	if (error == B_ENTRY_NOT_FOUND)
1469 		error = adminDirectory.CreateDirectory(kQueuedScriptsDirectoryName, &scriptsDirectory);
1470 	if (error != B_OK) {
1471 		ERROR("Failed to open queued scripts directory: %s\n", strerror(error));
1472 		return;
1473 	}
1474 
1475 	BDirectory rootDir(&fVolume->RootDirectoryRef());
1476 	for (PackageSet::iterator it = fAddedPackages.begin();
1477 		it != fAddedPackages.end(); ++it) {
1478 		Package* package = *it;
1479 		const BStringList& scripts = package->Info().PostInstallScripts();
1480 		for (int32 i = 0; i < scripts.CountStrings(); ++i) {
1481 			BPath scriptPath(&rootDir, scripts.StringAt(i));
1482 			status_t error = scriptPath.InitCheck();
1483 			if (error != B_OK) {
1484 				ERROR("Can't find script: %s\n", scripts.StringAt(i).String());
1485 				continue;
1486 			}
1487 
1488 			// symlink to the script
1489 			BSymLink scriptLink;
1490 			scriptsDirectory.CreateSymLink(scriptPath.Leaf(),
1491 				scriptPath.Path(), &scriptLink);
1492 			if (scriptLink.InitCheck() != B_OK) {
1493 				ERROR("Creating symlink failed: %s\n", strerror(scriptLink.InitCheck()));
1494 				continue;
1495 			}
1496 		}
1497 	}
1498 }
1499 
1500 
1501 void
1502 CommitTransactionHandler::_ExtractPackageContent(Package* package,
1503 	const BStringList& contentPaths, BDirectory& targetDirectory,
1504 	BDirectory& _extractedFilesDirectory)
1505 {
1506 	// check whether the subdirectory already exists
1507 	BString targetName(package->RevisionedNameThrows());
1508 
1509 	BEntry targetEntry;
1510 	status_t error = targetEntry.SetTo(&targetDirectory, targetName);
1511 	if (error != B_OK) {
1512 		throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY)
1513 			.SetPath1(_GetPath(
1514 				FSUtils::Entry(targetDirectory, targetName),
1515 				targetName))
1516 			.SetPackageName(package->FileName())
1517 			.SetSystemError(error);
1518 	}
1519 	if (targetEntry.Exists()) {
1520 		// nothing to do -- the very same version of the package has already
1521 		// been extracted
1522 		error = _extractedFilesDirectory.SetTo(&targetDirectory,
1523 			targetName);
1524 		if (error != B_OK) {
1525 			throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
1526 				.SetPath1(_GetPath(
1527 					FSUtils::Entry(targetDirectory, targetName),
1528 					targetName))
1529 				.SetPackageName(package->FileName())
1530 				.SetSystemError(error);
1531 		}
1532 		return;
1533 	}
1534 
1535 	// create the subdirectory with a temporary name (remove, if it already
1536 	// exists)
1537 	BString temporaryTargetName = BString().SetToFormat("%s.tmp",
1538 		targetName.String());
1539 	if (temporaryTargetName.IsEmpty())
1540 		throw std::bad_alloc();
1541 
1542 	error = targetEntry.SetTo(&targetDirectory, temporaryTargetName);
1543 	if (error != B_OK) {
1544 		throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY)
1545 			.SetPath1(_GetPath(
1546 				FSUtils::Entry(targetDirectory, temporaryTargetName),
1547 				temporaryTargetName))
1548 			.SetPackageName(package->FileName())
1549 			.SetSystemError(error);
1550 	}
1551 
1552 	if (targetEntry.Exists()) {
1553 		// remove pre-existing
1554 		error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry));
1555 		if (error != B_OK) {
1556 			throw Exception(B_TRANSACTION_FAILED_TO_REMOVE_DIRECTORY)
1557 				.SetPath1(_GetPath(
1558 					FSUtils::Entry(targetDirectory, temporaryTargetName),
1559 					temporaryTargetName))
1560 				.SetPackageName(package->FileName())
1561 				.SetSystemError(error);
1562 		}
1563 	}
1564 
1565 	BDirectory& subDirectory = _extractedFilesDirectory;
1566 	FSTransaction::CreateOperation createSubDirectoryOperation(
1567 		&fFSTransaction,
1568 		FSUtils::Entry(targetDirectory, temporaryTargetName));
1569 	error = targetDirectory.CreateDirectory(temporaryTargetName,
1570 		&subDirectory);
1571 	if (error != B_OK) {
1572 		throw Exception(B_TRANSACTION_FAILED_TO_CREATE_DIRECTORY)
1573 			.SetPath1(_GetPath(
1574 				FSUtils::Entry(targetDirectory, temporaryTargetName),
1575 				temporaryTargetName))
1576 			.SetPackageName(package->FileName())
1577 			.SetSystemError(error);
1578 	}
1579 
1580 	createSubDirectoryOperation.Finished();
1581 
1582 	// extract
1583 	NotOwningEntryRef packageRef(package->EntryRef());
1584 
1585 	int32 contentPathCount = contentPaths.CountStrings();
1586 	for (int32 i = 0; i < contentPathCount; i++) {
1587 		const char* contentPath = contentPaths.StringAt(i);
1588 
1589 		error = FSUtils::ExtractPackageContent(FSUtils::Entry(packageRef),
1590 			contentPath, FSUtils::Entry(subDirectory));
1591 		if (error != B_OK) {
1592 			throw Exception(B_TRANSACTION_FAILED_TO_EXTRACT_PACKAGE_FILE)
1593 				.SetPath1(contentPath)
1594 				.SetPackageName(package->FileName())
1595 				.SetSystemError(error);
1596 		}
1597 	}
1598 
1599 	// tag all entries with the package attribute
1600 	_TagPackageEntriesRecursively(subDirectory, targetName, true);
1601 
1602 	// rename the subdirectory
1603 	error = targetEntry.Rename(targetName);
1604 	if (error != B_OK) {
1605 		throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE)
1606 			.SetPath1(_GetPath(
1607 				FSUtils::Entry(targetDirectory, temporaryTargetName),
1608 				temporaryTargetName))
1609 			.SetPath2(targetName)
1610 			.SetPackageName(package->FileName())
1611 			.SetSystemError(error);
1612 	}
1613 
1614 	// keep the directory, regardless of whether the transaction is rolled
1615 	// back
1616 	createSubDirectoryOperation.Unregister();
1617 }
1618 
1619 
1620 status_t
1621 CommitTransactionHandler::_OpenPackagesSubDirectory(const RelativePath& path,
1622 	bool create, BDirectory& _directory)
1623 {
1624 	// open the packages directory
1625 	BDirectory directory;
1626 	status_t error = directory.SetTo(&fVolume->PackagesDirectoryRef());
1627 	if (error != B_OK) {
1628 		ERROR("CommitTransactionHandler::_OpenPackagesSubDirectory(): failed "
1629 			"to open packages directory: %s\n", strerror(error));
1630 		RETURN_ERROR(error);
1631 	}
1632 
1633 	return FSUtils::OpenSubDirectory(directory, path, create, _directory);
1634 }
1635 
1636 
1637 status_t
1638 CommitTransactionHandler::_OpenPackagesFile(
1639 	const RelativePath& subDirectoryPath, const char* fileName, uint32 openMode,
1640 	BFile& _file, BEntry* _entry)
1641 {
1642 	BDirectory directory;
1643 	if (!subDirectoryPath.IsEmpty()) {
1644 		status_t error = _OpenPackagesSubDirectory(subDirectoryPath,
1645 			(openMode & B_CREATE_FILE) != 0, directory);
1646 		if (error != B_OK) {
1647 			ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to "
1648 				"open packages subdirectory \"%s\": %s\n",
1649 				subDirectoryPath.ToString().String(), strerror(error));
1650 			RETURN_ERROR(error);
1651 		}
1652 	} else {
1653 		status_t error = directory.SetTo(&fVolume->PackagesDirectoryRef());
1654 		if (error != B_OK) {
1655 			ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to "
1656 				"open packages directory: %s\n", strerror(error));
1657 			RETURN_ERROR(error);
1658 		}
1659 	}
1660 
1661 	BEntry stackEntry;
1662 	BEntry& entry = _entry != NULL ? *_entry : stackEntry;
1663 	status_t error = entry.SetTo(&directory, fileName);
1664 	if (error != B_OK) {
1665 		ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to get "
1666 			"entry for file: %s", strerror(error));
1667 		RETURN_ERROR(error);
1668 	}
1669 
1670 	return _file.SetTo(&entry, openMode);
1671 }
1672 
1673 
1674 void
1675 CommitTransactionHandler::_WriteActivationFile(
1676 	const RelativePath& directoryPath, const char* fileName,
1677 	const PackageSet& toActivate, const PackageSet& toDeactivate,
1678 	BEntry& _entry)
1679 {
1680 	// create the content
1681 	BString activationFileContent;
1682 	_CreateActivationFileContent(toActivate, toDeactivate,
1683 		activationFileContent);
1684 
1685 	// write the file
1686 	status_t error = _WriteTextFile(directoryPath, fileName,
1687 		activationFileContent, _entry);
1688 	if (error != B_OK) {
1689 		BString filePath = directoryPath.ToString() << '/' << fileName;
1690 		throw Exception(B_TRANSACTION_FAILED_TO_WRITE_ACTIVATION_FILE)
1691 			.SetPath1(_GetPath(
1692 				FSUtils::Entry(fVolume->PackagesDirectoryRef(), filePath),
1693 				filePath))
1694 			.SetSystemError(error);
1695 	}
1696 }
1697 
1698 
1699 void
1700 CommitTransactionHandler::_CreateActivationFileContent(
1701 	const PackageSet& toActivate, const PackageSet& toDeactivate,
1702 	BString& _content)
1703 {
1704 	BString activationFileContent;
1705 	for (PackageFileNameHashTable::Iterator it
1706 			= fVolumeState->ByFileNameIterator();
1707 		Package* package = it.Next();) {
1708 		if (package->IsActive()
1709 			&& toDeactivate.find(package) == toDeactivate.end()) {
1710 			int32 length = activationFileContent.Length();
1711 			activationFileContent << package->FileName() << '\n';
1712 			if (activationFileContent.Length()
1713 					< length + package->FileName().Length() + 1) {
1714 				throw Exception(B_TRANSACTION_NO_MEMORY);
1715 			}
1716 		}
1717 	}
1718 
1719 	for (PackageSet::const_iterator it = toActivate.begin();
1720 		it != toActivate.end(); ++it) {
1721 		Package* package = *it;
1722 		int32 length = activationFileContent.Length();
1723 		activationFileContent << package->FileName() << '\n';
1724 		if (activationFileContent.Length()
1725 				< length + package->FileName().Length() + 1) {
1726 			throw Exception(B_TRANSACTION_NO_MEMORY);
1727 		}
1728 	}
1729 
1730 	_content = activationFileContent;
1731 }
1732 
1733 
1734 status_t
1735 CommitTransactionHandler::_WriteTextFile(const RelativePath& directoryPath,
1736 	const char* fileName, const BString& content, BEntry& _entry)
1737 {
1738 	BFile file;
1739 	status_t error = _OpenPackagesFile(directoryPath,
1740 		fileName, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE, file, &_entry);
1741 	if (error != B_OK) {
1742 		ERROR("CommitTransactionHandler::_WriteTextFile(): failed to create "
1743 			"file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName,
1744 			strerror(error));
1745 		return error;
1746 	}
1747 
1748 	ssize_t bytesWritten = file.Write(content.String(),
1749 		content.Length());
1750 	if (bytesWritten < 0) {
1751 		ERROR("CommitTransactionHandler::_WriteTextFile(): failed to write "
1752 			"file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName,
1753 			strerror(bytesWritten));
1754 		return bytesWritten;
1755 	}
1756 
1757 	return B_OK;
1758 }
1759 
1760 
1761 void
1762 CommitTransactionHandler::_ChangePackageActivation(
1763 	const PackageSet& packagesToActivate,
1764 	const PackageSet& packagesToDeactivate)
1765 {
1766 	INFORM("CommitTransactionHandler::_ChangePackageActivation(): activating "
1767 		"%zu, deactivating %zu packages\n", packagesToActivate.size(),
1768 		packagesToDeactivate.size());
1769 
1770 	// write the temporary package activation file
1771 	BEntry activationFileEntry;
1772 	_WriteActivationFile(RelativePath(kAdminDirectoryName),
1773 		kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate,
1774 		activationFileEntry);
1775 
1776 	// notify packagefs
1777 	if (fVolumeStateIsActive) {
1778 		_ChangePackageActivationIOCtl(packagesToActivate, packagesToDeactivate);
1779 	} else {
1780 		// TODO: Notify packagefs that active packages have been moved or do
1781 		// node monitoring in packagefs!
1782 	}
1783 
1784 	// rename the temporary activation file to the final file
1785 	status_t error = activationFileEntry.Rename(kActivationFileName, true);
1786 	if (error != B_OK) {
1787 		throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE)
1788 			.SetPath1(_GetPath(
1789 				FSUtils::Entry(activationFileEntry),
1790 				activationFileEntry.Name()))
1791 			.SetPath2(kActivationFileName)
1792 			.SetSystemError(error);
1793 
1794 // TODO: We should probably try to revert the activation changes, though that
1795 // will fail, if this method has been called in response to node monitoring
1796 // events. Alternatively moving the package activation file could be made part
1797 // of the ioctl(), since packagefs should be able to undo package changes until
1798 // the very end, unless running out of memory. In the end the situation would be
1799 // bad anyway, though, since the activation file may refer to removed packages
1800 // and things would be in an inconsistent state after rebooting.
1801 	}
1802 
1803 	// Update our state, i.e. remove deactivated packages and mark activated
1804 	// packages accordingly.
1805 	fVolumeState->ActivationChanged(packagesToActivate, packagesToDeactivate);
1806 }
1807 
1808 
1809 void
1810 CommitTransactionHandler::_ChangePackageActivationIOCtl(
1811 	const PackageSet& packagesToActivate,
1812 	const PackageSet& packagesToDeactivate)
1813 {
1814 	// compute the size of the allocation we need for the activation change
1815 	// request
1816 	int32 itemCount = packagesToActivate.size() + packagesToDeactivate.size();
1817 	size_t requestSize = sizeof(PackageFSActivationChangeRequest)
1818 		+ itemCount * sizeof(PackageFSActivationChangeItem);
1819 
1820 	for (PackageSet::iterator it = packagesToActivate.begin();
1821 		 it != packagesToActivate.end(); ++it) {
1822 		requestSize += (*it)->FileName().Length() + 1;
1823 	}
1824 
1825 	for (PackageSet::iterator it = packagesToDeactivate.begin();
1826 		 it != packagesToDeactivate.end(); ++it) {
1827 		requestSize += (*it)->FileName().Length() + 1;
1828 	}
1829 
1830 	// allocate and prepare the request
1831 	PackageFSActivationChangeRequest* request
1832 		= (PackageFSActivationChangeRequest*)malloc(requestSize);
1833 	if (request == NULL)
1834 		throw Exception(B_TRANSACTION_NO_MEMORY);
1835 	MemoryDeleter requestDeleter(request);
1836 
1837 	request->itemCount = itemCount;
1838 
1839 	PackageFSActivationChangeItem* item = &request->items[0];
1840 	char* nameBuffer = (char*)(item + itemCount);
1841 
1842 	for (PackageSet::iterator it = packagesToActivate.begin();
1843 		it != packagesToActivate.end(); ++it, item++) {
1844 		_FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it,
1845 			nameBuffer);
1846 	}
1847 
1848 	for (PackageSet::iterator it = packagesToDeactivate.begin();
1849 		it != packagesToDeactivate.end(); ++it, item++) {
1850 		_FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it,
1851 			nameBuffer);
1852 	}
1853 
1854 	// issue the request
1855 	FileDescriptorCloser fd(fVolume->OpenRootDirectory());
1856 	if (!fd.IsSet()) {
1857 		throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
1858 			.SetPath1(_GetPath(
1859 				FSUtils::Entry(fVolume->RootDirectoryRef()),
1860 				"<packagefs root>"))
1861 			.SetSystemError(fd.Get());
1862 	}
1863 
1864 	if (ioctl(fd.Get(), PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request,
1865 		requestSize) != 0) {
1866 // TODO: We need more error information and error handling!
1867 		throw Exception(B_TRANSACTION_FAILED_TO_CHANGE_PACKAGE_ACTIVATION)
1868 			.SetSystemError(errno);
1869 	}
1870 }
1871 
1872 
1873 void
1874 CommitTransactionHandler::_FillInActivationChangeItem(
1875 	PackageFSActivationChangeItem* item, PackageFSActivationChangeType type,
1876 	Package* package, char*& nameBuffer)
1877 {
1878 	item->type = type;
1879 	item->packageDeviceID = package->NodeRef().device;
1880 	item->packageNodeID = package->NodeRef().node;
1881 	item->nameLength = package->FileName().Length();
1882 	item->parentDeviceID = fVolume->PackagesDeviceID();
1883 	item->parentDirectoryID = fVolume->PackagesDirectoryID();
1884 	item->name = nameBuffer;
1885 	strcpy(nameBuffer, package->FileName());
1886 	nameBuffer += package->FileName().Length() + 1;
1887 }
1888 
1889 
1890 bool
1891 CommitTransactionHandler::_IsSystemPackage(Package* package)
1892 {
1893 	// package name should be "haiku[_<arch>]"
1894 	const BString& name = package->Info().Name();
1895 	if (!name.StartsWith("haiku"))
1896 		return false;
1897 	if (name.Length() == 5)
1898 		return true;
1899 	if (name[5] != '_')
1900 		return false;
1901 
1902 	BPackageArchitecture architecture;
1903 	return BPackageInfo::GetArchitectureByName(name.String() + 6, architecture)
1904 		== B_OK;
1905 }
1906 
1907 
1908 void
1909 CommitTransactionHandler::_AddIssue(const TransactionIssueBuilder& builder)
1910 {
1911 	fResult.AddIssue(builder.BuildIssue(fCurrentPackage));
1912 }
1913 
1914 
1915 /*static*/ BString
1916 CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry,
1917 	const BString& fallback)
1918 {
1919 	BString path = entry.Path();
1920 	return path.IsEmpty() ? fallback : path;
1921 }
1922 
1923 
1924 /*static*/ void
1925 CommitTransactionHandler::_TagPackageEntriesRecursively(BDirectory& directory,
1926 	const BString& value, bool nonDirectoriesOnly)
1927 {
1928 	char buffer[offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH];
1929 	dirent *entry = (dirent*)buffer;
1930 	while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
1931 		if (strcmp(entry->d_name, ".") == 0
1932 			|| strcmp(entry->d_name, "..") == 0) {
1933 			continue;
1934 		}
1935 
1936 		// determine type
1937 		struct stat st;
1938 		status_t error = directory.GetStatFor(entry->d_name, &st);
1939 		if (error != B_OK) {
1940 			throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY)
1941 				.SetPath1(_GetPath(
1942 					FSUtils::Entry(directory, entry->d_name),
1943 					entry->d_name))
1944 				.SetSystemError(error);
1945 		}
1946 		bool isDirectory = S_ISDIR(st.st_mode);
1947 
1948 		// open the node and set the attribute
1949 		BNode stackNode;
1950 		BDirectory stackDirectory;
1951 		BNode* node;
1952 		if (isDirectory) {
1953 			node = &stackDirectory;
1954 			error = stackDirectory.SetTo(&directory, entry->d_name);
1955 		} else {
1956 			node = &stackNode;
1957 			error = stackNode.SetTo(&directory, entry->d_name);
1958 		}
1959 
1960 		if (error != B_OK) {
1961 			throw Exception(isDirectory
1962 					? B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY
1963 					: B_TRANSACTION_FAILED_TO_OPEN_FILE)
1964 				.SetPath1(_GetPath(
1965 					FSUtils::Entry(directory, entry->d_name),
1966 					entry->d_name))
1967 				.SetSystemError(error);
1968 		}
1969 
1970 		if (!isDirectory || !nonDirectoriesOnly) {
1971 			error = node->WriteAttrString(kPackageFileAttribute, &value);
1972 			if (error != B_OK) {
1973 				throw Exception(B_TRANSACTION_FAILED_TO_WRITE_FILE_ATTRIBUTE)
1974 					.SetPath1(_GetPath(
1975 						FSUtils::Entry(directory, entry->d_name),
1976 						entry->d_name))
1977 					.SetSystemError(error);
1978 			}
1979 		}
1980 
1981 		// recurse
1982 		if (isDirectory) {
1983 			_TagPackageEntriesRecursively(stackDirectory, value,
1984 				nonDirectoriesOnly);
1985 		}
1986 	}
1987 }
1988 
1989 
1990 /*static*/ status_t
1991 CommitTransactionHandler::_AssertEntriesAreEqual(const BEntry& entry,
1992 	const BDirectory* directory)
1993 {
1994 	BFile a;
1995 	status_t status = a.SetTo(&entry, B_READ_ONLY);
1996 	if (status != B_OK)
1997 		return status;
1998 
1999 	BFile b;
2000 	status = b.SetTo(directory, entry.Name(), B_READ_ONLY);
2001 	if (status != B_OK)
2002 		return status;
2003 
2004 	off_t aSize;
2005 	status = a.GetSize(&aSize);
2006 	if (status != B_OK)
2007 		return status;
2008 
2009 	off_t bSize;
2010 	status = b.GetSize(&bSize);
2011 	if (status != B_OK)
2012 		return status;
2013 
2014 	if (aSize != bSize)
2015 		return B_FILE_EXISTS;
2016 
2017 	const size_t bufferSize = 4096;
2018 	uint8 aBuffer[bufferSize];
2019 	uint8 bBuffer[bufferSize];
2020 
2021 	while (aSize > 0) {
2022 		ssize_t aRead = a.Read(aBuffer, bufferSize);
2023 		ssize_t bRead = b.Read(bBuffer, bufferSize);
2024 		if (aRead < 0 || aRead != bRead)
2025 			return B_FILE_EXISTS;
2026 		if (memcmp(aBuffer, bBuffer, aRead) != 0)
2027 			return B_FILE_EXISTS;
2028 		aSize -= aRead;
2029 	}
2030 
2031 	INFORM("CommitTransactionHandler::_AssertEntriesAreEqual(): "
2032 		"Package file '%s' already exists in target folder "
2033 		"with equal contents\n", entry.Name());
2034 	return B_OK;
2035 }
2036