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