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