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