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