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	fWritableFilesDirectory(),
130	fAddedGroups(),
131	fAddedUsers(),
132	fFSTransaction(),
133	fResult(result),
134	fCurrentPackage(NULL)
135{
136}
137
138
139CommitTransactionHandler::~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
156void
157CommitTransactionHandler::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
181void
182CommitTransactionHandler::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
198void
199CommitTransactionHandler::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
223void
224CommitTransactionHandler::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
238void
239CommitTransactionHandler::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
256VolumeState*
257CommitTransactionHandler::DetachVolumeState()
258{
259	VolumeState* result = fVolumeState;
260	fVolumeState = NULL;
261	return result;
262}
263
264
265void
266CommitTransactionHandler::_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
289void
290CommitTransactionHandler::_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
382void
383CommitTransactionHandler::_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
412void
413CommitTransactionHandler::_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
485void
486CommitTransactionHandler::_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
543void
544CommitTransactionHandler::_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
613void
614CommitTransactionHandler::_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
636void
637CommitTransactionHandler::_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
664void
665CommitTransactionHandler::_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
734void
735CommitTransactionHandler::_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
795void
796CommitTransactionHandler::_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
868void
869CommitTransactionHandler::_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
1168void
1169CommitTransactionHandler::_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
1223void
1224CommitTransactionHandler::_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
1273void
1274CommitTransactionHandler::_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
1296void
1297CommitTransactionHandler::_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
1313void
1314CommitTransactionHandler::_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
1355void
1356CommitTransactionHandler::_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
1401void
1402CommitTransactionHandler::_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
1520status_t
1521CommitTransactionHandler::_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
1537status_t
1538CommitTransactionHandler::_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
1574void
1575CommitTransactionHandler::_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
1599void
1600CommitTransactionHandler::_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
1634status_t
1635CommitTransactionHandler::_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
1661void
1662CommitTransactionHandler::_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
1709void
1710CommitTransactionHandler::_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
1774void
1775CommitTransactionHandler::_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
1791bool
1792CommitTransactionHandler::_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
1809void
1810CommitTransactionHandler::_AddIssue(const TransactionIssueBuilder& builder)
1811{
1812	fResult.AddIssue(builder.BuildIssue(fCurrentPackage));
1813}
1814
1815
1816/*static*/ BString
1817CommitTransactionHandler::_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
1826CommitTransactionHandler::_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