xref: /haiku/src/servers/package/Volume.cpp (revision 6d9f0064ff9f6a547bc5e2365278919ab346b5a6)
1 /*
2  * Copyright 2013, 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 "Volume.h"
11 
12 #include <errno.h>
13 #include <grp.h>
14 #include <pwd.h>
15 #include <stdlib.h>
16 #include <sys/stat.h>
17 #include <time.h>
18 #include <unistd.h>
19 
20 #include <string>
21 
22 #include <Directory.h>
23 #include <Entry.h>
24 #include <File.h>
25 #include <Looper.h>
26 #include <MessageRunner.h>
27 #include <NodeMonitor.h>
28 #include <Path.h>
29 
30 #include <package/solver/Solver.h>
31 #include <package/solver/SolverPackage.h>
32 #include <package/solver/SolverProblem.h>
33 #include <package/solver/SolverProblemSolution.h>
34 #include <package/solver/SolverRepository.h>
35 #include <package/solver/SolverResult.h>
36 
37 #include <AutoDeleter.h>
38 #include <AutoLocker.h>
39 #include <CopyEngine.h>
40 #include <NotOwningEntryRef.h>
41 #include <package/DaemonDefs.h>
42 #include <package/PackagesDirectoryDefs.h>
43 #include <RemoveEngine.h>
44 
45 #include "DebugSupport.h"
46 #include "Exception.h"
47 #include "FSTransaction.h"
48 
49 
50 using namespace BPackageKit::BPrivate;
51 
52 typedef std::set<std::string> StringSet;
53 
54 
55 static const char* const kPackageFileNameExtension = ".hpkg";
56 static const char* const kAdminDirectoryName
57 	= PACKAGES_DIRECTORY_ADMIN_DIRECTORY;
58 static const char* const kActivationFileName
59 	= PACKAGES_DIRECTORY_ACTIVATION_FILE;
60 static const char* const kTemporaryActivationFileName
61 	= PACKAGES_DIRECTORY_ACTIVATION_FILE ".tmp";
62 static const char* const kWritableFilesDirectoryName = "writable-files";
63 static const char* const kPackageFileAttribute = "SYS:PACKAGE";
64 
65 static const bigtime_t kHandleNodeMonitorEvents = 'nmon';
66 
67 static const bigtime_t kNodeMonitorEventHandlingDelay = 500000;
68 static const bigtime_t kCommunicationTimeout = 1000000;
69 
70 
71 // #pragma mark - Listener
72 
73 
74 Volume::Listener::~Listener()
75 {
76 }
77 
78 
79 // #pragma mark - NodeMonitorEvent
80 
81 
82 struct Volume::NodeMonitorEvent
83 	: public DoublyLinkedListLinkImpl<NodeMonitorEvent> {
84 public:
85 	NodeMonitorEvent(const BString& entryName, bool created)
86 		:
87 		fEntryName(entryName),
88 		fCreated(created)
89 	{
90 	}
91 
92 	const BString& EntryName() const
93 	{
94 		return fEntryName;
95 	}
96 
97 	bool WasCreated() const
98 	{
99 		return fCreated;
100 	}
101 
102 private:
103 	BString	fEntryName;
104 	bool	fCreated;
105 };
106 
107 
108 // #pragma mark - CommitTransactionHandler
109 
110 
111 struct Volume::CommitTransactionHandler {
112 	CommitTransactionHandler(Volume* volume,
113 		const PackageSet& packagesAlreadyAdded,
114 		const PackageSet& packagesAlreadyRemoved)
115 		:
116 		fVolume(volume),
117 		fPackagesToActivate(),
118 		fPackagesToDeactivate(),
119 		fAddedPackages(),
120 		fRemovedPackages(),
121 		fPackagesAlreadyAdded(packagesAlreadyAdded),
122 		fPackagesAlreadyRemoved(packagesAlreadyRemoved),
123 		fAddedGroups(),
124 		fAddedUsers(),
125 		fFSTransaction()
126 	{
127 	}
128 
129 	~CommitTransactionHandler()
130 	{
131 		// Delete Package objects we created in case of error (on success
132 		// fPackagesToActivate will be empty).
133 		int32 count = fPackagesToActivate.CountItems();
134 		for (int32 i = 0; i < count; i++) {
135 			Package* package = fPackagesToActivate.ItemAt(i);
136 			if (fPackagesAlreadyAdded.find(package)
137 					== fPackagesAlreadyAdded.end()) {
138 				delete package;
139 			}
140 		}
141 	}
142 
143 	void HandleRequest(BMessage* request, BMessage* reply)
144 	{
145 		status_t error;
146 		BActivationTransaction transaction(request, &error);
147 		if (error == B_OK)
148 			error = transaction.InitCheck();
149 		if (error != B_OK) {
150 			if (error == B_NO_MEMORY)
151 				throw Exception(B_NO_MEMORY);
152 			throw Exception(B_DAEMON_BAD_REQUEST);
153 		}
154 
155 		HandleRequest(transaction, reply);
156 	}
157 
158 	void HandleRequest(const BActivationTransaction& transaction,
159 		BMessage* reply)
160 	{
161 		// check the change count
162 		if (transaction.ChangeCount() != fVolume->fChangeCount)
163 			throw Exception(B_DAEMON_CHANGE_COUNT_MISMATCH);
164 
165 		// collect the packages to deactivate
166 		_GetPackagesToDeactivate(transaction);
167 
168 		// read the packages to activate
169 		_ReadPackagesToActivate(transaction);
170 
171 		// anything to do at all?
172 		if (fPackagesToActivate.IsEmpty() &&  fPackagesToDeactivate.empty()) {
173 			throw Exception(B_DAEMON_BAD_REQUEST,
174 				"no packages to activate or deactivate");
175 		}
176 
177 		_ApplyChanges(reply);
178 	}
179 
180 	void HandleRequest(const PackageSet& packagesAdded,
181 		const PackageSet& packagesRemoved)
182 	{
183 		// Copy package sets to fPackagesToActivate/fPackagesToDeactivate. The
184 		// given sets are assumed to be identical to the ones specified in the
185 		// constructor invocation (fPackagesAlreadyAdded,
186 		// fPackagesAlreadyRemoved).
187 		for (PackageSet::const_iterator it = packagesAdded.begin();
188 			it != packagesAdded.end(); ++it) {
189 			if (!fPackagesToActivate.AddItem(*it))
190 				throw std::bad_alloc();
191 		}
192 
193 		fPackagesToDeactivate = packagesRemoved;
194 
195 		_ApplyChanges(NULL);
196 	}
197 
198 	void Revert()
199 	{
200 		// move packages to activate back to transaction directory
201 		_RevertAddPackagesToActivate();
202 
203 		// move packages to deactivate back to packages directory
204 		_RevertRemovePackagesToDeactivate();
205 
206 		// revert user and group changes
207 		_RevertUserGroupChanges();
208 
209 		// Revert all other FS operations, i.e. the writable files changes as
210 		// well as the creation of the old state directory.
211 		fFSTransaction.RollBack();
212 	}
213 
214 	const BString& OldStateDirectoryName() const
215 	{
216 		return fOldStateDirectoryName;
217 	}
218 
219 private:
220 	typedef BObjectList<Package> PackageList;
221 
222 	void _GetPackagesToDeactivate(const BActivationTransaction& transaction)
223 	{
224 		// get the number of packages to deactivate
225 		const BStringList& packagesToDeactivate
226 			= transaction.PackagesToDeactivate();
227 		int32 packagesToDeactivateCount = packagesToDeactivate.CountStrings();
228 		if (packagesToDeactivateCount == 0)
229 			return;
230 
231 		for (int32 i = 0; i < packagesToDeactivateCount; i++) {
232 			BString packageName = packagesToDeactivate.StringAt(i);
233 			Package* package = fVolume->fPackagesByFileName.Lookup(packageName);
234 			if (package == NULL) {
235 				throw Exception(B_DAEMON_NO_SUCH_PACKAGE, "no such package",
236 					packageName);
237 			}
238 
239 			fPackagesToDeactivate.insert(package);
240 
241 			if (fPackagesAlreadyRemoved.find(package)
242 					== fPackagesAlreadyRemoved.end()) {
243 				package->IncrementEntryRemovedIgnoreLevel();
244 			}
245 		}
246 	}
247 
248 	void _ReadPackagesToActivate(const BActivationTransaction& transaction)
249 	{
250 		// get the number of packages to activate
251 		const BStringList& packagesToActivate
252 			= transaction.PackagesToActivate();
253 		int32 packagesToActivateCount = packagesToActivate.CountStrings();
254 		if (packagesToActivateCount == 0)
255 			return;
256 
257 		// check the transaction directory name -- we only allow a simple
258 		// subdirectory of the admin directory
259 		const BString& transactionDirectoryName
260 			= transaction.TransactionDirectoryName();
261 		if (transactionDirectoryName.IsEmpty()
262 			|| transactionDirectoryName.FindFirst('/') >= 0
263 			|| transactionDirectoryName == "."
264 			|| transactionDirectoryName == "..") {
265 			throw Exception(B_DAEMON_BAD_REQUEST);
266 		}
267 
268 		// open the directory
269 		RelativePath directoryPath(kAdminDirectoryName,
270 			transactionDirectoryName);
271 		BDirectory directory;
272 		status_t error = fVolume->_OpenPackagesSubDirectory(directoryPath,
273 			false, directory);
274 		if (error != B_OK)
275 			throw Exception(error, "failed to open transaction directory");
276 
277 		error = directory.GetNodeRef(&fTransactionDirectoryRef);
278 		if (error != B_OK) {
279 			throw Exception(error,
280 				"failed to get transaction directory node ref");
281 		}
282 
283 		// read the packages
284 		for (int32 i = 0; i < packagesToActivateCount; i++) {
285 			BString packageName = packagesToActivate.StringAt(i);
286 
287 			// make sure it doesn't clash with an already existing package
288 			Package* package = fVolume->fPackagesByFileName.Lookup(packageName);
289 			if (package != NULL) {
290 				if (fPackagesAlreadyAdded.find(package)
291 						!= fPackagesAlreadyAdded.end()) {
292 					if (!fPackagesToActivate.AddItem(package))
293 						throw Exception(B_NO_MEMORY);
294 					continue;
295 				}
296 
297 				if (fPackagesToDeactivate.find(package)
298 						== fPackagesToDeactivate.end()) {
299 					throw Exception(B_DAEMON_PACKAGE_ALREADY_EXISTS, NULL,
300 						packageName);
301 				}
302 			}
303 
304 			// read the package
305 			entry_ref entryRef;
306 			entryRef.device = fTransactionDirectoryRef.device;
307 			entryRef.directory = fTransactionDirectoryRef.node;
308 			if (entryRef.set_name(packageName) != B_OK)
309 				throw Exception(B_NO_MEMORY);
310 
311 			package = new(std::nothrow) Package;
312 			if (package == NULL || !fPackagesToActivate.AddItem(package)) {
313 				delete package;
314 				throw Exception(B_NO_MEMORY);
315 			}
316 
317 			error = package->Init(entryRef);
318 			if (error != B_OK)
319 				throw Exception(error, "failed to read package", packageName);
320 
321 			package->IncrementEntryCreatedIgnoreLevel();
322 		}
323 	}
324 
325 	void _ApplyChanges(BMessage* reply)
326 	{
327 		// create an old state directory
328 		_CreateOldStateDirectory(reply);
329 
330 		// move packages to deactivate to old state directory
331 		_RemovePackagesToDeactivate();
332 
333 		// move packages to activate to packages directory
334 		_AddPackagesToActivate();
335 
336 		// activate/deactivate packages
337 		fVolume->_ChangePackageActivation(fAddedPackages, fRemovedPackages);
338 
339 		// run post-installation scripts
340 		_RunPostInstallScripts();
341 
342 		// removed packages have been deleted, new packages shall not be deleted
343 		fAddedPackages.clear();
344 		fRemovedPackages.clear();
345 		fPackagesToActivate.MakeEmpty(false);
346 		fPackagesToDeactivate.clear();
347 	}
348 
349 	void _CreateOldStateDirectory(BMessage* reply)
350 	{
351 		// construct a nice name from the current date and time
352 		time_t nowSeconds = time(NULL);
353 		struct tm now;
354 		BString baseName;
355 		if (localtime_r(&nowSeconds, &now) != NULL) {
356 			baseName.SetToFormat("state_%d-%02d-%02d_%02d:%02d:%02d",
357 				1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour,
358 				now.tm_min, now.tm_sec);
359 		} else
360 			baseName = "state";
361 
362 		if (baseName.IsEmpty())
363 			throw Exception(B_NO_MEMORY);
364 
365 		// make sure the directory doesn't exist yet
366 		BDirectory adminDirectory;
367 		status_t error = fVolume->_OpenPackagesSubDirectory(
368 			RelativePath(kAdminDirectoryName), true, adminDirectory);
369 		if (error != B_OK)
370 			throw Exception(error, "failed to open administrative directory");
371 
372 		int uniqueId = 1;
373 		BString directoryName = baseName;
374 		while (BEntry(&adminDirectory, directoryName).Exists()) {
375 			directoryName.SetToFormat("%s-%d", baseName.String(), uniqueId++);
376 			if (directoryName.IsEmpty())
377 				throw Exception(B_NO_MEMORY);
378 		}
379 
380 		// create the directory
381 		FSTransaction::CreateOperation createOldStateDirectoryOperation(
382 			&fFSTransaction, FSUtils::Entry(adminDirectory, directoryName));
383 
384 		error = adminDirectory.CreateDirectory(directoryName,
385 			&fOldStateDirectory);
386 		if (error != B_OK)
387 			throw Exception(error, "failed to create old state directory");
388 
389 		createOldStateDirectoryOperation.Finished();
390 
391 		fOldStateDirectoryName = directoryName;
392 
393 		// write the old activation file
394 		BEntry activationFile;
395 		error = fVolume->_WriteActivationFile(
396 			RelativePath(kAdminDirectoryName, directoryName),
397 			kActivationFileName, PackageSet(), PackageSet(), activationFile);
398 		if (error != B_OK)
399 			throw Exception(error, "failed to write old activation file");
400 
401 		// add the old state directory to the reply
402 		if (reply != NULL) {
403 			error = reply->AddString("old state", fOldStateDirectoryName);
404 			if (error != B_OK)
405 				throw Exception(error, "failed to add field to reply");
406 		}
407 	}
408 
409 	void _RemovePackagesToDeactivate()
410 	{
411 		if (fPackagesToDeactivate.empty())
412 			return;
413 
414 		for (PackageSet::const_iterator it = fPackagesToDeactivate.begin();
415 			it != fPackagesToDeactivate.end(); ++it) {
416 			Package* package = *it;
417 			if (fPackagesAlreadyRemoved.find(package)
418 					!= fPackagesAlreadyRemoved.end()) {
419 				fRemovedPackages.insert(package);
420 				continue;
421 			}
422 
423 			// get a BEntry for the package
424 			NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef,
425 				package->FileName());
426 
427 			BEntry entry;
428 			status_t error = entry.SetTo(&entryRef);
429 			if (error != B_OK) {
430 				throw Exception(error, "failed to get package entry",
431 					package->FileName());
432 			}
433 
434 			// move entry
435 			fRemovedPackages.insert(package);
436 
437 			error = entry.MoveTo(&fOldStateDirectory);
438 			if (error != B_OK) {
439 				fRemovedPackages.erase(package);
440 				throw Exception(error,
441 					"failed to move old package from packages directory",
442 					package->FileName());
443 			}
444 		}
445 	}
446 
447 	void _AddPackagesToActivate()
448 	{
449 		if (fPackagesToActivate.IsEmpty())
450 			return;
451 
452 		// open packages directory
453 		BDirectory packagesDirectory;
454 		status_t error
455 			= packagesDirectory.SetTo(&fVolume->fPackagesDirectoryRef);
456 		if (error != B_OK)
457 			throw Exception(error, "failed to open packages directory");
458 
459 		int32 count = fPackagesToActivate.CountItems();
460 		for (int32 i = 0; i < count; i++) {
461 			Package* package = fPackagesToActivate.ItemAt(i);
462 			if (fPackagesAlreadyAdded.find(package)
463 					!= fPackagesAlreadyAdded.end()) {
464 				fAddedPackages.insert(package);
465 				_PreparePackageToActivate(package);
466 				continue;
467 			}
468 
469 			// get a BEntry for the package
470 			entry_ref entryRef;
471 			entryRef.device = fTransactionDirectoryRef.device;
472 			entryRef.directory = fTransactionDirectoryRef.node;
473 			if (entryRef.set_name(package->FileName()) != B_OK)
474 				throw Exception(B_NO_MEMORY);
475 
476 			BEntry entry;
477 			error = entry.SetTo(&entryRef);
478 			if (error != B_OK) {
479 				throw Exception(error, "failed to get package entry",
480 					package->FileName());
481 			}
482 
483 			// move entry
484 			fAddedPackages.insert(package);
485 
486 			error = entry.MoveTo(&packagesDirectory);
487 			if (error != B_OK) {
488 				fAddedPackages.erase(package);
489 				throw Exception(error,
490 					"failed to move new package to packages directory",
491 					package->FileName());
492 			}
493 
494 			// also add the package to the volume
495 			fVolume->_AddPackage(package);
496 
497 			_PreparePackageToActivate(package);
498 		}
499 	}
500 
501 	void _PreparePackageToActivate(Package* package)
502 	{
503 		// add groups
504 		const BStringList& groups = package->Info().Groups();
505 		int32 count = groups.CountStrings();
506 		for (int32 i = 0; i < count; i++)
507 			_AddGroup(package, groups.StringAt(i));
508 
509 		// add users
510 		const BObjectList<BUser>& users = package->Info().Users();
511 		for (int32 i = 0; const BUser* user = users.ItemAt(i); i++)
512 			_AddUser(package, *user);
513 
514 		// handle global writable files
515 		_AddGlobalWritableFiles(package);
516 	}
517 
518 	void _AddGroup(Package* package, const BString& groupName)
519 	{
520 		// Check whether the group already exists.
521 		char buffer[256];
522 		struct group groupBuffer;
523 		struct group* groupFound;
524 		int error = getgrnam_r(groupName, &groupBuffer, buffer, sizeof(buffer),
525 			&groupFound);
526 		if ((error == 0 && groupFound != NULL) || error == ERANGE)
527 			return;
528 
529 		// add it
530 		fAddedGroups.insert(groupName.String());
531 
532 		std::string commandLine("groupadd ");
533 		commandLine += FSUtils::ShellEscapeString(groupName).String();
534 
535 		if (system(commandLine.c_str()) != 0) {
536 			fAddedGroups.erase(groupName.String());
537 			throw Exception(error,
538 				BString().SetToFormat("failed to add group \%s\"",
539 					groupName.String()),
540 				package->FileName());
541 		}
542 	}
543 
544 	void _AddUser(Package* package, const BUser& user)
545 	{
546 		// Check whether the user already exists.
547 		char buffer[256];
548 		struct passwd passwdBuffer;
549 		struct passwd* passwdFound;
550 		int error = getpwnam_r(user.Name(), &passwdBuffer, buffer,
551 			sizeof(buffer), &passwdFound);
552 		if ((error == 0 && passwdFound != NULL) || error == ERANGE)
553 			return;
554 
555 		// add it
556 		fAddedUsers.insert(user.Name().String());
557 
558 		std::string commandLine("useradd ");
559 
560 		if (!user.RealName().IsEmpty()) {
561 			commandLine += std::string("-n ")
562 				+ FSUtils::ShellEscapeString(user.RealName()).String() + " ";
563 		}
564 
565 		if (!user.Home().IsEmpty()) {
566 			commandLine += std::string("-d ")
567 				+ FSUtils::ShellEscapeString(user.Home()).String() + " ";
568 		}
569 
570 		if (!user.Shell().IsEmpty()) {
571 			commandLine += std::string("-s ")
572 				+ FSUtils::ShellEscapeString(user.Shell()).String() + " ";
573 		}
574 
575 		if (!user.Groups().IsEmpty()) {
576 			commandLine += std::string("-g ")
577 				+ FSUtils::ShellEscapeString(user.Groups().First()).String()
578 				+ " ";
579 		}
580 
581 		commandLine += FSUtils::ShellEscapeString(user.Name()).String();
582 
583 		if (system(commandLine.c_str()) != 0) {
584 			fAddedUsers.erase(user.Name().String());
585 			throw Exception(error,
586 				BString().SetToFormat("failed to add user \%s\"",
587 					user.Name().String()),
588 				package->FileName());
589 		}
590 
591 		// add the supplementary groups
592 		int32 groupCount = user.Groups().CountStrings();
593 		for (int32 i = 1; i < groupCount; i++) {
594 			commandLine = std::string("groupmod -A ")
595 				+ FSUtils::ShellEscapeString(user.Name()).String()
596 				+ " "
597 				+ FSUtils::ShellEscapeString(user.Groups().StringAt(i))
598 					.String();
599 			if (system(commandLine.c_str()) != 0) {
600 				fAddedUsers.erase(user.Name().String());
601 				throw Exception(error,
602 					BString().SetToFormat("failed to add user \%s\" to group "
603 						"\"%s\"", user.Name().String(),
604 						user.Groups().StringAt(i).String()),
605 					package->FileName());
606 			}
607 		}
608 	}
609 
610 	void _AddGlobalWritableFiles(Package* package)
611 	{
612 		// get the list of included files
613 		const BObjectList<BGlobalWritableFileInfo>& files
614 			= package->Info().GlobalWritableFileInfos();
615 		BStringList contentPaths;
616 		for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i);
617 			i++) {
618 			if (file->IsIncluded() && !contentPaths.Add(file->Path()))
619 				throw std::bad_alloc();
620 		}
621 
622 		if (contentPaths.IsEmpty())
623 			return;
624 
625 		// Open the root directory of the installation location where we will
626 		// extract the files -- that's the volume's root directory.
627 		BDirectory rootDirectory;
628 		status_t error = rootDirectory.SetTo(&fVolume->fRootDirectoryRef);
629 		if (error != B_OK) {
630 			throw Exception(error,
631 				BString().SetToFormat("failed to get the root directory "
632 					"for writable files"),
633 				package->FileName());
634 		}
635 
636 		// Open writable-files directory in the administrative directory.
637 		if (fWritableFilesDirectory.InitCheck() != B_OK) {
638 			error = fVolume->_OpenPackagesSubDirectory(
639 				RelativePath(kAdminDirectoryName, kWritableFilesDirectoryName),
640 				true, fWritableFilesDirectory);
641 
642 			if (error != B_OK) {
643 				throw Exception(error,
644 					BString().SetToFormat("failed to get the backup directory "
645 						"for writable files"),
646 					package->FileName());
647 			}
648 		}
649 
650 		// extract files into a subdir of the writable-files directory
651 		BDirectory extractedFilesDirectory;
652 		_ExtractPackageContent(package, contentPaths,
653 			fWritableFilesDirectory, extractedFilesDirectory);
654 
655 		for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i);
656 			i++) {
657 			if (file->IsIncluded()) {
658 				_AddGlobalWritableFile(package, *file, rootDirectory,
659 					extractedFilesDirectory);
660 			}
661 		}
662 	}
663 
664 	void _AddGlobalWritableFile(Package* package,
665 		const BGlobalWritableFileInfo& file, const BDirectory& rootDirectory,
666 		const BDirectory& extractedFilesDirectory)
667 	{
668 		// Map the path name to the actual target location. Currently this only
669 		// concerns "settings/", which is mapped to "settings/global/".
670 		BString targetPath(file.Path());
671 		if (fVolume->fMountType == PACKAGE_FS_MOUNT_TYPE_HOME) {
672 			if (targetPath == "settings"
673 				|| targetPath.StartsWith("settings/")) {
674 				targetPath.Insert("/global", 8);
675 				if (targetPath.Length() == file.Path().Length())
676 					throw std::bad_alloc();
677 			}
678 		}
679 
680 		// open parent directory of the source entry
681 		const char* lastSlash = strrchr(file.Path(), '/');
682 		const BDirectory* sourceDirectory;
683 		BDirectory stackSourceDirectory;
684 		if (lastSlash != NULL) {
685 			sourceDirectory = &stackSourceDirectory;
686 			BString sourceParentPath(file.Path(),
687 				lastSlash - file.Path().String());
688 			if (sourceParentPath.Length() == 0)
689 				throw std::bad_alloc();
690 
691 			status_t error = stackSourceDirectory.SetTo(
692 				&extractedFilesDirectory, sourceParentPath);
693 			if (error != B_OK) {
694 				throw Exception(error,
695 					BString().SetToFormat("failed to open directory \"%s\"",
696 						_GetPath(
697 							FSUtils::Entry(extractedFilesDirectory,
698 								sourceParentPath),
699 							sourceParentPath).String()),
700 					package->FileName());
701 			}
702 		} else {
703 			sourceDirectory = &extractedFilesDirectory;
704 		}
705 
706 		// open parent directory of the target entry -- create, if necessary
707 		FSUtils::Path relativeSourcePath(file.Path());
708 		lastSlash = strrchr(targetPath, '/');
709 		if (lastSlash != NULL) {
710 			BString targetParentPath(targetPath,
711 				lastSlash - targetPath.String());
712 			if (targetParentPath.Length() == 0)
713 				throw std::bad_alloc();
714 
715 			BDirectory targetDirectory;
716 			status_t error = FSUtils::OpenSubDirectory(rootDirectory,
717 				RelativePath(targetParentPath), true, targetDirectory);
718 			if (error != B_OK) {
719 				throw Exception(error,
720 					BString().SetToFormat("failed to open/create directory "
721 						"\"%s\"",
722 						_GetPath(
723 							FSUtils::Entry(rootDirectory,targetParentPath),
724 							targetParentPath).String()),
725 					package->FileName());
726 			}
727 			_AddGlobalWritableFileRecurse(package, *sourceDirectory,
728 				relativeSourcePath, targetDirectory, lastSlash + 1,
729 				file.UpdateType());
730 		} else {
731 			_AddGlobalWritableFileRecurse(package, *sourceDirectory,
732 				relativeSourcePath, rootDirectory, targetPath,
733 				file.UpdateType());
734 		}
735 	}
736 
737 	void _AddGlobalWritableFileRecurse(Package* package,
738 		const BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath,
739 		const BDirectory& targetDirectory, const char* targetName,
740 		BWritableFileUpdateType updateType)
741 	{
742 		// * If the file doesn't exist, just copy the extracted one.
743 		// * If the file does exist, compare with the previous original version:
744 		//   * If unchanged, just overwrite it.
745 		//   * If changed, leave it to the user for now. When we support merging
746 		//     first back the file up, then try the merge.
747 
748 		// Check whether the target location exists and what type the entry at
749 		// both locations are.
750 		struct stat targetStat;
751 		if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) {
752 			// target doesn't exist -- just copy
753 			PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
754 				"couldn't get stat for writable file, copying...\n");
755 			FSTransaction::CreateOperation copyOperation(&fFSTransaction,
756 				FSUtils::Entry(targetDirectory, targetName));
757 			status_t error = BCopyEngine(BCopyEngine::COPY_RECURSIVELY)
758 				.CopyEntry(
759 					FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()),
760 					FSUtils::Entry(targetDirectory, targetName));
761 			if (error != B_OK) {
762 				if (targetDirectory.GetStatFor(targetName, &targetStat) == B_OK)
763 					copyOperation.Finished();
764 
765 				throw Exception(error,
766 					BString().SetToFormat("failed to copy entry \"%s\"",
767 						_GetPath(
768 							FSUtils::Entry(sourceDirectory,
769 								relativeSourcePath.Leaf()),
770 							relativeSourcePath).String()),
771 					package->FileName());
772 			}
773 			copyOperation.Finished();
774 			return;
775 		}
776 
777 		struct stat sourceStat;
778 		status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(),
779 			&sourceStat);
780 		if (error != B_OK) {
781 			throw Exception(error,
782 				BString().SetToFormat("failed to get stat data for entry "
783 					"\"%s\"",
784 					_GetPath(
785 						FSUtils::Entry(targetDirectory, targetName),
786 						targetName).String()),
787 				package->FileName());
788 		}
789 
790 		if ((sourceStat.st_mode & S_IFMT) != (targetStat.st_mode & S_IFMT)
791 			|| (!S_ISDIR(sourceStat.st_mode) && !S_ISREG(sourceStat.st_mode)
792 				&& !S_ISLNK(sourceStat.st_mode))) {
793 			// Source and target entry types don't match or this is an entry
794 			// we cannot handle. The user must handle this manually.
795 			PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
796 				"writable file exists, but type doesn't match previous type\n");
797 // TODO: Notify user!
798 			return;
799 		}
800 
801 		if (S_ISDIR(sourceStat.st_mode)) {
802 			// entry is a directory -- recurse
803 			BDirectory sourceSubDirectory;
804 			error = sourceSubDirectory.SetTo(&sourceDirectory,
805 				relativeSourcePath.Leaf());
806 			if (error != B_OK) {
807 				throw Exception(error,
808 					BString().SetToFormat("failed to open directory \"%s\"",
809 						_GetPath(
810 							FSUtils::Entry(sourceDirectory,
811 								relativeSourcePath.Leaf()),
812 							relativeSourcePath).String()),
813 					package->FileName());
814 			}
815 
816 			BDirectory targetSubDirectory;
817 			error = targetSubDirectory.SetTo(&targetDirectory, targetName);
818 			if (error != B_OK) {
819 				throw Exception(error,
820 					BString().SetToFormat("failed to open directory \"%s\"",
821 						_GetPath(
822 							FSUtils::Entry(targetDirectory, targetName),
823 							targetName).String()),
824 					package->FileName());
825 			}
826 
827 			entry_ref entry;
828 			while (sourceSubDirectory.GetNextRef(&entry) == B_OK) {
829 				relativeSourcePath.AppendComponent(entry.name);
830 				_AddGlobalWritableFileRecurse(package, sourceSubDirectory,
831 					relativeSourcePath, targetSubDirectory, entry.name,
832 					updateType);
833 				relativeSourcePath.RemoveLastComponent();
834 			}
835 
836 			PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
837 				"writable directory, recursion done\n");
838 			return;
839 		}
840 
841 		// get the package the target file originated from
842 		BString originalPackage;
843 		if (BNode(&targetDirectory, targetName).ReadAttrString(
844 				kPackageFileAttribute, &originalPackage) != B_OK) {
845 			// Can't determine the original package. The user must handle this
846 			// manually.
847 // TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD!
848 			PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
849 				"failed to get SYS:PACKAGE attribute\n");
850 			return;
851 		}
852 
853 		// If that's our package, we're happy.
854 		if (originalPackage == package->RevisionedNameThrows()) {
855 			PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
856 				"file tagged with same package version we're activating\n");
857 			return;
858 		}
859 
860 		// Check, whether the writable-files directory for the original package
861 		// exists.
862 		BString originalRelativeSourcePath = BString().SetToFormat("%s/%s",
863 			originalPackage.String(), relativeSourcePath.ToCString());
864 		if (originalRelativeSourcePath.IsEmpty())
865 			throw std::bad_alloc();
866 
867 		struct stat originalPackageStat;
868 		if (fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath,
869 				&originalPackageStat) != B_OK
870 			|| (sourceStat.st_mode & S_IFMT)
871 				!= (originalPackageStat.st_mode & S_IFMT)) {
872 			// Original entry doesn't exist (either we don't have the data from
873 			// the original package or the entry really didn't exist) or its
874 			// type differs from the expected one. The user must handle this
875 			// manually.
876 			PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
877 				"original \"%s\" doesn't exist or has other type\n",
878 				_GetPath(FSUtils::Entry(fWritableFilesDirectory,
879 						originalRelativeSourcePath),
880 					originalRelativeSourcePath).String());
881 			return;
882 // TODO: Notify user!
883 		}
884 
885 		if (S_ISREG(sourceStat.st_mode)) {
886 			// compare file content
887 			bool equal;
888 			error = FSUtils::CompareFileContent(
889 				FSUtils::Entry(fWritableFilesDirectory,
890 					originalRelativeSourcePath),
891 				FSUtils::Entry(targetDirectory, targetName),
892 				equal);
893 			// TODO: Merge support!
894 			if (error != B_OK || !equal) {
895 				// The comparison failed or the files differ. The user must
896 				// handle this manually.
897 				PRINT("Volume::CommitTransactionHandler::"
898 					"_AddGlobalWritableFile(): "
899 					"file comparison failed (%s) or files aren't equal\n",
900 					strerror(error));
901 				return;
902 // TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD!
903 			}
904 		} else {
905 			// compare symlinks
906 			bool equal;
907 			error = FSUtils::CompareSymLinks(
908 				FSUtils::Entry(fWritableFilesDirectory,
909 					originalRelativeSourcePath),
910 				FSUtils::Entry(targetDirectory, targetName),
911 				equal);
912 			if (error != B_OK || !equal) {
913 				// The comparison failed or the symlinks differ. The user must
914 				// handle this manually.
915 				PRINT("Volume::CommitTransactionHandler::"
916 					"_AddGlobalWritableFile(): "
917 					"symlink comparison failed (%s) or symlinks aren't equal\n",
918 					strerror(error));
919 				return;
920 // TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD!
921 			}
922 		}
923 
924 		// Replace the existing file/symlink. We do that in two steps: First
925 		// copy the new file to a neighoring location, then move-replace the
926 		// old file.
927 		BString tempTargetName;
928 		tempTargetName.SetToFormat("%s.%s", targetName,
929 			package->RevisionedNameThrows().String());
930 		if (tempTargetName.IsEmpty())
931 			throw std::bad_alloc();
932 
933 		// copy
934 		FSTransaction::CreateOperation copyOperation(&fFSTransaction,
935 			FSUtils::Entry(targetDirectory, tempTargetName));
936 
937 		error = BCopyEngine(BCopyEngine::UNLINK_DESTINATION).CopyEntry(
938 			FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()),
939 			FSUtils::Entry(targetDirectory, tempTargetName));
940 		if (error != B_OK) {
941 			throw Exception(error,
942 				BString().SetToFormat("failed to copy entry \"%s\"",
943 					_GetPath(
944 						FSUtils::Entry(sourceDirectory,
945 							relativeSourcePath.Leaf()),
946 						relativeSourcePath).String()),
947 				package->FileName());
948 		}
949 
950 		copyOperation.Finished();
951 
952 		// rename
953 		FSTransaction::RemoveOperation renameOperation(&fFSTransaction,
954 			FSUtils::Entry(targetDirectory, targetName),
955 			FSUtils::Entry(fWritableFilesDirectory,
956 				originalRelativeSourcePath));
957 
958 		BEntry targetEntry;
959 		error = targetEntry.SetTo(&targetDirectory, tempTargetName);
960 		if (error == B_OK)
961 			error = targetEntry.Rename(targetName, true);
962 		if (error != B_OK) {
963 			throw Exception(error,
964 				BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"",
965 					_GetPath(
966 						FSUtils::Entry(targetDirectory, tempTargetName),
967 						tempTargetName).String(),
968 					targetName),
969 				package->FileName());
970 		}
971 
972 		renameOperation.Finished();
973 		copyOperation.Unregister();
974 	}
975 
976 	void _RevertAddPackagesToActivate()
977 	{
978 		if (fAddedPackages.empty())
979 			return;
980 
981 		// open transaction directory
982 		BDirectory transactionDirectory;
983 		status_t error = transactionDirectory.SetTo(&fTransactionDirectoryRef);
984 		if (error != B_OK) {
985 			ERROR("failed to open transaction directory: %s\n",
986 				strerror(error));
987 		}
988 
989 		for (PackageSet::iterator it = fAddedPackages.begin();
990 			it != fAddedPackages.end(); ++it) {
991 			// remove package from the volume
992 			Package* package = *it;
993 
994 			if (fPackagesAlreadyAdded.find(package)
995 					!= fPackagesAlreadyAdded.end()) {
996 				continue;
997 			}
998 
999 			fVolume->_RemovePackage(package);
1000 
1001 			if (transactionDirectory.InitCheck() != B_OK)
1002 				continue;
1003 
1004 			// get BEntry for the package
1005 			NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef,
1006 				package->FileName());
1007 
1008 			BEntry entry;
1009 			error = entry.SetTo(&entryRef);
1010 			if (error != B_OK) {
1011 				ERROR("failed to get entry for package \"%s\": %s\n",
1012 					package->FileName().String(), strerror(error));
1013 				continue;
1014 			}
1015 
1016 			// move entry
1017 			error = entry.MoveTo(&transactionDirectory);
1018 			if (error != B_OK) {
1019 				ERROR("failed to move new package \"%s\" back to transaction "
1020 					"directory: %s\n", package->FileName().String(),
1021 					strerror(error));
1022 				continue;
1023 			}
1024 		}
1025 	}
1026 
1027 	void _RevertRemovePackagesToDeactivate()
1028 	{
1029 		if (fRemovedPackages.empty())
1030 			return;
1031 
1032 		// open packages directory
1033 		BDirectory packagesDirectory;
1034 		status_t error
1035 			= packagesDirectory.SetTo(&fVolume->fPackagesDirectoryRef);
1036 		if (error != B_OK) {
1037 			throw Exception(error, "failed to open packages directory");
1038 			ERROR("failed to open packages directory: %s\n",
1039 				strerror(error));
1040 			return;
1041 		}
1042 
1043 		for (PackageSet::iterator it = fRemovedPackages.begin();
1044 			it != fRemovedPackages.end(); ++it) {
1045 			Package* package = *it;
1046 			if (fPackagesAlreadyRemoved.find(package)
1047 					!= fPackagesAlreadyRemoved.end()) {
1048 				continue;
1049 			}
1050 
1051 			// get a BEntry for the package
1052 			BEntry entry;
1053 			status_t error = entry.SetTo(&fOldStateDirectory,
1054 				package->FileName());
1055 			if (error != B_OK) {
1056 				ERROR("failed to get entry for package \"%s\": %s\n",
1057 					package->FileName().String(), strerror(error));
1058 				continue;
1059 			}
1060 
1061 			// move entry
1062 			error = entry.MoveTo(&packagesDirectory);
1063 			if (error != B_OK) {
1064 				ERROR("failed to move old package \"%s\" back to packages "
1065 					"directory: %s\n", package->FileName().String(),
1066 					strerror(error));
1067 				continue;
1068 			}
1069 		}
1070 	}
1071 
1072 	void _RevertUserGroupChanges()
1073 	{
1074 		// delete users
1075 		for (StringSet::const_iterator it = fAddedUsers.begin();
1076 			it != fAddedUsers.end(); ++it) {
1077 			std::string commandLine("userdel ");
1078 			commandLine += FSUtils::ShellEscapeString(it->c_str()).String();
1079 			if (system(commandLine.c_str()) != 0)
1080 				ERROR("failed to remove user \"%s\"\n", it->c_str());
1081 		}
1082 
1083 		// delete groups
1084 		for (StringSet::const_iterator it = fAddedGroups.begin();
1085 			it != fAddedGroups.end(); ++it) {
1086 			std::string commandLine("groupdel ");
1087 			commandLine += FSUtils::ShellEscapeString(it->c_str()).String();
1088 			if (system(commandLine.c_str()) != 0)
1089 				ERROR("failed to remove group \"%s\"\n", it->c_str());
1090 		}
1091 	}
1092 
1093 	void _RunPostInstallScripts()
1094 	{
1095 		for (PackageSet::iterator it = fAddedPackages.begin();
1096 			it != fAddedPackages.end(); ++it) {
1097 			Package* package = *it;
1098 			const BStringList& scripts = package->Info().PostInstallScripts();
1099 			int32 count = scripts.CountStrings();
1100 			for (int32 i = 0; i < count; i++)
1101 				_RunPostInstallScript(package, scripts.StringAt(i));
1102 		}
1103 	}
1104 
1105 	void _RunPostInstallScript(Package* package, const BString& script)
1106 	{
1107 		BDirectory rootDir(&fVolume->fRootDirectoryRef);
1108 		BPath scriptPath(&rootDir, script);
1109 		status_t error = scriptPath.InitCheck();
1110 		if (error != B_OK) {
1111 			ERROR("Volume::CommitTransactionHandler::_RunPostInstallScript(): "
1112 				"failed get path of post-installation script \"%s\" of package "
1113 				"%s: %s\n", script.String(), package->FileName().String(),
1114 				strerror(error));
1115 // TODO: Notify the user!
1116 			return;
1117 		}
1118 
1119 		if (system(scriptPath.Path()) != 0) {
1120 			ERROR("Volume::CommitTransactionHandler::_RunPostInstallScript(): "
1121 				"running post-installation script \"%s\" of package %s "
1122 				"failed: %s\n", script.String(), package->FileName().String(),
1123 				strerror(error));
1124 // TODO: Notify the user!
1125 		}
1126 	}
1127 
1128 	static BString _GetPath(const FSUtils::Entry& entry,
1129 		const BString& fallback)
1130 	{
1131 		BString path = entry.Path();
1132 		return path.IsEmpty() ? fallback : path;
1133 	}
1134 
1135 	void _ExtractPackageContent(Package* package,
1136 		const BStringList& contentPaths, BDirectory& targetDirectory,
1137 		BDirectory& _extractedFilesDirectory)
1138 	{
1139 		// check whether the subdirectory already exists
1140 		BString targetName(package->RevisionedNameThrows());
1141 
1142 		BEntry targetEntry;
1143 		status_t error = targetEntry.SetTo(&targetDirectory, targetName);
1144 		if (error != B_OK) {
1145 			throw Exception(error,
1146 				BString().SetToFormat("failed to init entry \"%s\"",
1147 					_GetPath(
1148 						FSUtils::Entry(targetDirectory, targetName),
1149 						targetName).String()),
1150 				package->FileName());
1151 		}
1152 		if (targetEntry.Exists()) {
1153 			// nothing to do -- the very same version of the package has already
1154 			// been extracted
1155 			error = _extractedFilesDirectory.SetTo(&targetDirectory,
1156 				targetName);
1157 			if (error != B_OK) {
1158 				throw Exception(error,
1159 					BString().SetToFormat("failed to open directory \"%s\"",
1160 						_GetPath(
1161 							FSUtils::Entry(targetDirectory, targetName),
1162 							targetName).String()),
1163 					package->FileName());
1164 			}
1165 			return;
1166 		}
1167 
1168 		// create the subdirectory with a temporary name (remove, if it already
1169 		// exists)
1170 		BString temporaryTargetName = BString().SetToFormat("%s.tmp",
1171 			targetName.String());
1172 		if (temporaryTargetName.IsEmpty())
1173 			throw std::bad_alloc();
1174 
1175 		error = targetEntry.SetTo(&targetDirectory, temporaryTargetName);
1176 		if (error != B_OK) {
1177 			throw Exception(error,
1178 				BString().SetToFormat("failed to init entry \"%s\"",
1179 					_GetPath(
1180 						FSUtils::Entry(targetDirectory, temporaryTargetName),
1181 						temporaryTargetName).String()),
1182 				package->FileName());
1183 		}
1184 
1185 		if (targetEntry.Exists()) {
1186 			// remove pre-existing
1187 			error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry));
1188 			if (error != B_OK) {
1189 				throw Exception(error,
1190 					BString().SetToFormat("failed to remove directory \"%s\"",
1191 						_GetPath(
1192 							FSUtils::Entry(targetDirectory,
1193 								temporaryTargetName),
1194 							temporaryTargetName).String()),
1195 					package->FileName());
1196 			}
1197 		}
1198 
1199 		BDirectory& subDirectory = _extractedFilesDirectory;
1200 		FSTransaction::CreateOperation createSubDirectoryOperation(
1201 			&fFSTransaction,
1202 			FSUtils::Entry(targetDirectory, temporaryTargetName));
1203 		error = targetDirectory.CreateDirectory(temporaryTargetName,
1204 			&subDirectory);
1205 		if (error != B_OK) {
1206 			throw Exception(error,
1207 				BString().SetToFormat("failed to create directory \"%s\"",
1208 					_GetPath(
1209 						FSUtils::Entry(targetDirectory, temporaryTargetName),
1210 						temporaryTargetName).String()),
1211 				package->FileName());
1212 		}
1213 
1214 		createSubDirectoryOperation.Finished();
1215 
1216 		// extract
1217 		NotOwningEntryRef packageRef(fVolume->fPackagesDirectoryRef,
1218 			package->FileName());
1219 
1220 		int32 contentPathCount = contentPaths.CountStrings();
1221 		for (int32 i = 0; i < contentPathCount; i++) {
1222 			const char* contentPath = contentPaths.StringAt(i);
1223 
1224 			error = FSUtils::ExtractPackageContent(FSUtils::Entry(packageRef),
1225 				contentPath, FSUtils::Entry(subDirectory));
1226 			if (error != B_OK) {
1227 				throw Exception(error,
1228 					BString().SetToFormat(
1229 						"failed to extract \"%s\" from package", contentPath),
1230 					package->FileName());
1231 			}
1232 		}
1233 
1234 		// tag all entries with the package attribute
1235 		error = _TagPackageEntriesRecursively(subDirectory, targetName, true);
1236 		if (error != B_OK) {
1237 			throw Exception(error,
1238 				BString().SetToFormat("failed to tag extract files in \"%s\" "
1239 					"with package attribute",
1240 					_GetPath(
1241 						FSUtils::Entry(targetDirectory, temporaryTargetName),
1242 						temporaryTargetName).String()),
1243 				package->FileName());
1244 		}
1245 
1246 		// rename the subdirectory
1247 		error = targetEntry.Rename(targetName);
1248 		if (error != B_OK) {
1249 			throw Exception(error,
1250 				BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"",
1251 					_GetPath(
1252 						FSUtils::Entry(targetDirectory, temporaryTargetName),
1253 						temporaryTargetName).String(),
1254 					targetName.String()),
1255 				package->FileName());
1256 		}
1257 
1258 		// keep the directory, regardless of whether the transaction is rolled
1259 		// back
1260 		createSubDirectoryOperation.Unregister();
1261 	}
1262 
1263 	static status_t _TagPackageEntriesRecursively(BDirectory& directory,
1264 		const BString& value, bool nonDirectoriesOnly)
1265 	{
1266 		char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH];
1267 		dirent *entry = (dirent*)buffer;
1268 		while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
1269 			if (strcmp(entry->d_name, ".") == 0
1270 				|| strcmp(entry->d_name, "..") == 0) {
1271 				continue;
1272 			}
1273 
1274 			// determine type
1275 			struct stat st;
1276 			status_t error = directory.GetStatFor(entry->d_name, &st);
1277 			if (error != B_OK)
1278 				return error;
1279 			bool isDirectory = S_ISDIR(st.st_mode);
1280 
1281 			// open the node and set the attribute
1282 			BNode stackNode;
1283 			BDirectory stackDirectory;
1284 			BNode* node;
1285 			if (isDirectory) {
1286 				node = &stackDirectory;
1287 				error = stackDirectory.SetTo(&directory, entry->d_name);
1288 			} else {
1289 				node = &stackNode;
1290 				error = stackNode.SetTo(&directory, entry->d_name);
1291 			}
1292 
1293 			if (error != B_OK)
1294 				return error;
1295 
1296 			if (!isDirectory || !nonDirectoriesOnly) {
1297 				error = node->WriteAttrString(kPackageFileAttribute, &value);
1298 				if (error != B_OK)
1299 					return error;
1300 			}
1301 
1302 			// recurse
1303 			if (isDirectory) {
1304 				error = _TagPackageEntriesRecursively(stackDirectory, value,
1305 					nonDirectoriesOnly);
1306 				if (error != B_OK)
1307 					return error;
1308 			}
1309 		}
1310 
1311 		return B_OK;
1312 	}
1313 
1314 private:
1315 	Volume*				fVolume;
1316 	PackageList			fPackagesToActivate;
1317 	PackageSet			fPackagesToDeactivate;
1318 	PackageSet			fAddedPackages;
1319 	PackageSet			fRemovedPackages;
1320 	const PackageSet&	fPackagesAlreadyAdded;
1321 	const PackageSet&	fPackagesAlreadyRemoved;
1322 	BDirectory			fOldStateDirectory;
1323 	BString				fOldStateDirectoryName;
1324 	node_ref			fTransactionDirectoryRef;
1325 	BDirectory			fWritableFilesDirectory;
1326 	StringSet			fAddedGroups;
1327 	StringSet			fAddedUsers;
1328 	FSTransaction		fFSTransaction;
1329 };
1330 
1331 
1332 // #pragma mark - Volume
1333 
1334 
1335 Volume::Volume(BLooper* looper)
1336 	:
1337 	BHandler(),
1338 	fPath(),
1339 	fMountType(PACKAGE_FS_MOUNT_TYPE_CUSTOM),
1340 	fRootDirectoryRef(),
1341 	fPackagesDirectoryRef(),
1342 	fRoot(NULL),
1343 	fListener(NULL),
1344 	fPackagesByFileName(),
1345 	fPackagesByNodeRef(),
1346 	fPendingNodeMonitorEventsLock("pending node monitor events"),
1347 	fPendingNodeMonitorEvents(),
1348 	fNodeMonitorEventHandleTime(0),
1349 	fPackagesToBeActivated(),
1350 	fPackagesToBeDeactivated(),
1351 	fChangeCount(0),
1352 	fLocationInfoReply(B_MESSAGE_GET_INSTALLATION_LOCATION_INFO_REPLY)
1353 {
1354 	looper->AddHandler(this);
1355 }
1356 
1357 
1358 Volume::~Volume()
1359 {
1360 	Unmounted();
1361 		// need for error case in InitPackages()
1362 
1363 	fPackagesByFileName.Clear();
1364 
1365 	Package* package = fPackagesByNodeRef.Clear(true);
1366 	while (package != NULL) {
1367 		Package* next = package->NodeRefHashTableNext();
1368 		delete package;
1369 		package = next;
1370 	}
1371 }
1372 
1373 
1374 status_t
1375 Volume::Init(const node_ref& rootDirectoryRef, node_ref& _packageRootRef)
1376 {
1377 	if (fPackagesByFileName.Init() != B_OK || fPackagesByNodeRef.Init() != B_OK)
1378 		RETURN_ERROR(B_NO_MEMORY);
1379 
1380 	fRootDirectoryRef = rootDirectoryRef;
1381 
1382 	// open the root directory
1383 	BDirectory directory;
1384 	status_t error = directory.SetTo(&fRootDirectoryRef);
1385 	if (error != B_OK) {
1386 		ERROR("Volume::Init(): failed to open root directory: %s\n",
1387 			strerror(error));
1388 		RETURN_ERROR(error);
1389 	}
1390 
1391 	// get the directory path
1392 	BEntry entry;
1393 	error = directory.GetEntry(&entry);
1394 
1395 	BPath path;
1396 	if (error == B_OK)
1397 		error = entry.GetPath(&path);
1398 
1399 	if (error != B_OK) {
1400 		ERROR("Volume::Init(): failed to get root directory path: %s\n",
1401 			strerror(error));
1402 		RETURN_ERROR(error);
1403 	}
1404 
1405 	fPath = path.Path();
1406 	if (fPath.IsEmpty())
1407 		RETURN_ERROR(B_NO_MEMORY);
1408 
1409 	// get a volume info from the FS
1410 	int fd = directory.Dup();
1411 	if (fd < 0) {
1412 		ERROR("Volume::Init(): failed to get root directory FD: %s\n",
1413 			strerror(fd));
1414 		RETURN_ERROR(fd);
1415 	}
1416 	FileDescriptorCloser fdCloser(fd);
1417 
1418 	PackageFSVolumeInfo info;
1419 	if (ioctl(fd, PACKAGE_FS_OPERATION_GET_VOLUME_INFO, &info, sizeof(info))
1420 			!= 0) {
1421 		ERROR("Volume::Init(): failed to get volume info: %s\n",
1422 			strerror(errno));
1423 		RETURN_ERROR(errno);
1424 	}
1425 
1426 	fMountType = info.mountType;
1427 	fPackagesDirectoryRef.device = info.packagesDeviceID;
1428 	fPackagesDirectoryRef.node = info.packagesDirectoryID;
1429 
1430 	_packageRootRef.device = info.rootDeviceID;
1431 	_packageRootRef.node = info.rootDirectoryID;
1432 
1433 	return B_OK;
1434 }
1435 
1436 
1437 status_t
1438 Volume::InitPackages(Listener* listener)
1439 {
1440 	// node-monitor the volume's packages directory
1441 	status_t error = watch_node(&fPackagesDirectoryRef, B_WATCH_DIRECTORY,
1442 		BMessenger(this));
1443 	if (error == B_OK) {
1444 		fListener = listener;
1445 	} else {
1446 		ERROR("Volume::InitPackages(): failed to start watching the packages "
1447 			"directory of the volume at \"%s\": %s\n",
1448 			fPath.String(), strerror(error));
1449 		// Not good, but not fatal. Only the manual package operations in the
1450 		// packages directory won't work correctly.
1451 	}
1452 
1453 	// read the packages directory and get the active packages
1454 	int fd = OpenRootDirectory();
1455 	if (fd < 0) {
1456 		ERROR("Volume::InitPackages(): failed to open root directory: %s\n",
1457 			strerror(fd));
1458 		RETURN_ERROR(fd);
1459 	}
1460 	FileDescriptorCloser fdCloser(fd);
1461 
1462 	error = _ReadPackagesDirectory();
1463 	if (error != B_OK)
1464 		RETURN_ERROR(error);
1465 
1466 	error = _GetActivePackages(fd);
1467 	if (error != B_OK)
1468 		RETURN_ERROR(error);
1469 
1470 	// create the admin directory, if it doesn't exist yet
1471 	BDirectory packagesDirectory;
1472 	if (packagesDirectory.SetTo(&fPackagesDirectoryRef) == B_OK) {
1473 		if (!BEntry(&packagesDirectory, kAdminDirectoryName).Exists())
1474 			packagesDirectory.CreateDirectory(kAdminDirectoryName, NULL);
1475 	}
1476 
1477 	return B_OK;
1478 }
1479 
1480 
1481 status_t
1482 Volume::AddPackagesToRepository(BSolverRepository& repository, bool activeOnly)
1483 {
1484 	for (PackageFileNameHashTable::Iterator it
1485 			= fPackagesByFileName.GetIterator(); it.HasNext();) {
1486 		Package* package = it.Next();
1487 		if (activeOnly && !package->IsActive())
1488 			continue;
1489 
1490 		status_t error = repository.AddPackage(package->Info());
1491 		if (error != B_OK) {
1492 			ERROR("Volume::AddPackagesToRepository(): failed to add package %s "
1493 				"to repository: %s\n", package->FileName().String(),
1494 				strerror(error));
1495 			return error;
1496 		}
1497 	}
1498 
1499 	return B_OK;
1500 }
1501 
1502 
1503 void
1504 Volume::InitialVerify(Volume* nextVolume, Volume* nextNextVolume)
1505 {
1506 INFORM("Volume::InitialVerify(%p, %p)\n", nextVolume, nextNextVolume);
1507 	// create the solver
1508 	BSolver* solver;
1509 	status_t error = BSolver::Create(solver);
1510 	if (error != B_OK) {
1511 		ERROR("Volume::InitialVerify(): failed to create solver: %s\n",
1512 			strerror(error));
1513 		return;
1514 	}
1515 	ObjectDeleter<BSolver> solverDeleter(solver);
1516 
1517 	// add a repository with all active packages
1518 	BSolverRepository repository;
1519 	error = _AddRepository(solver, repository, true, true);
1520 	if (error != B_OK) {
1521 		ERROR("Volume::InitialVerify(): failed to add repository: %s\n",
1522 			strerror(error));
1523 		return;
1524 	}
1525 
1526 	// add a repository for the next volume
1527 	BSolverRepository nextRepository;
1528 	if (nextVolume != NULL) {
1529 		nextRepository.SetPriority(1);
1530 		error = nextVolume->_AddRepository(solver, nextRepository, true, false);
1531 		if (error != B_OK) {
1532 			ERROR("Volume::InitialVerify(): failed to add repository: %s\n",
1533 				strerror(error));
1534 			return;
1535 		}
1536 	}
1537 
1538 	// add a repository for the next next volume
1539 	BSolverRepository nextNextRepository;
1540 	if (nextNextVolume != NULL) {
1541 		nextNextRepository.SetPriority(2);
1542 		error = nextNextVolume->_AddRepository(solver, nextNextRepository, true,
1543 			false);
1544 		if (error != B_OK) {
1545 			ERROR("Volume::InitialVerify(): failed to add repository: %s\n",
1546 				strerror(error));
1547 			return;
1548 		}
1549 	}
1550 
1551 	// verify
1552 	error = solver->VerifyInstallation();
1553 	if (error != B_OK) {
1554 		ERROR("Volume::InitialVerify(): failed to verify: %s\n",
1555 			strerror(error));
1556 		return;
1557 	}
1558 
1559 	if (!solver->HasProblems()) {
1560 		INFORM("Volume::InitialVerify(): volume at \"%s\" is consistent\n",
1561 			Path().String());
1562 		return;
1563 	}
1564 
1565 	// print the problems
1566 // TODO: Notify the user ...
1567 	INFORM("Volume::InitialVerify(): volume at \"%s\" has problems:\n",
1568 		Path().String());
1569 
1570 	int32 problemCount = solver->CountProblems();
1571 	for (int32 i = 0; i < problemCount; i++) {
1572 		BSolverProblem* problem = solver->ProblemAt(i);
1573 		INFORM("  %" B_PRId32 ": %s\n", i + 1, problem->ToString().String());
1574 		int32 solutionCount = problem->CountSolutions();
1575 		for (int32 k = 0; k < solutionCount; k++) {
1576 			const BSolverProblemSolution* solution = problem->SolutionAt(k);
1577 			INFORM("    solution %" B_PRId32 ":\n", k + 1);
1578 			int32 elementCount = solution->CountElements();
1579 			for (int32 l = 0; l < elementCount; l++) {
1580 				const BSolverProblemSolutionElement* element
1581 					= solution->ElementAt(l);
1582 				INFORM("      - %s\n", element->ToString().String());
1583 			}
1584 		}
1585 	}
1586 }
1587 
1588 
1589 void
1590 Volume::HandleGetLocationInfoRequest(BMessage* message)
1591 {
1592 	// If the cached reply message is up-to-date, just send it.
1593 	int64 changeCount;
1594 	if (fLocationInfoReply.FindInt64("change count", &changeCount) == B_OK
1595 		&& changeCount == fChangeCount) {
1596 		message->SendReply(&fLocationInfoReply, (BHandler*)NULL,
1597 			kCommunicationTimeout);
1598 		return;
1599 	}
1600 
1601 	// rebuild the reply message
1602 	fLocationInfoReply.MakeEmpty();
1603 
1604 	if (fLocationInfoReply.AddInt32("base directory device",
1605 			fRootDirectoryRef.device) != B_OK
1606 		|| fLocationInfoReply.AddInt64("base directory node",
1607 			fRootDirectoryRef.node) != B_OK
1608 		|| fLocationInfoReply.AddInt32("packages directory device",
1609 			fPackagesDirectoryRef.device) != B_OK
1610 		|| fLocationInfoReply.AddInt64("packages directory node",
1611 			fPackagesDirectoryRef.node) != B_OK) {
1612 		return;
1613 	}
1614 
1615 	for (PackageFileNameHashTable::Iterator it
1616 			= fPackagesByFileName.GetIterator(); it.HasNext();) {
1617 		Package* package = it.Next();
1618 		const char* fieldName = package->IsActive()
1619 			? "active packages" : "inactive packages";
1620 		BMessage packageArchive;
1621 		if (package->Info().Archive(&packageArchive) != B_OK
1622 			|| fLocationInfoReply.AddMessage(fieldName, &packageArchive)
1623 				!= B_OK) {
1624 			return;
1625 		}
1626 	}
1627 
1628 	if (fLocationInfoReply.AddInt64("change count", fChangeCount) != B_OK)
1629 		return;
1630 
1631 	message->SendReply(&fLocationInfoReply, (BHandler*)NULL,
1632 		kCommunicationTimeout);
1633 }
1634 
1635 
1636 void
1637 Volume::HandleCommitTransactionRequest(BMessage* message)
1638 {
1639 	// Prepare the reply in so far that we can at least set the error code
1640 	// without risk of failure.
1641 	BMessage reply(B_MESSAGE_COMMIT_TRANSACTION_REPLY);
1642 	if (reply.AddInt32("error", B_ERROR) != B_OK)
1643 		return;
1644 
1645 	// perform the request
1646 	PackageSet dummy;
1647 	CommitTransactionHandler handler(this, dummy, dummy);
1648 	int32 error;
1649 	try {
1650 		handler.HandleRequest(message, &reply);
1651 		error = B_DAEMON_OK;
1652 	} catch (Exception& exception) {
1653 		error = exception.Error();
1654 
1655 		if (!exception.ErrorMessage().IsEmpty())
1656 			reply.AddString("error message", exception.ErrorMessage());
1657 		if (!exception.PackageName().IsEmpty())
1658 			reply.AddString("error package", exception.PackageName());
1659 	} catch (std::bad_alloc& exception) {
1660 		error = B_NO_MEMORY;
1661 	}
1662 
1663 	// revert on error
1664 	if (error != B_DAEMON_OK)
1665 		handler.Revert();
1666 
1667 	// send the reply
1668 	reply.ReplaceInt32("error", error);
1669 	message->SendReply(&reply, (BHandler*)NULL, kCommunicationTimeout);
1670 }
1671 
1672 
1673 void
1674 Volume::Unmounted()
1675 {
1676 	if (fListener != NULL) {
1677 		stop_watching(BMessenger(this));
1678 		fListener = NULL;
1679 	}
1680 
1681 	if (BLooper* looper = Looper())
1682 		looper->RemoveHandler(this);
1683 }
1684 
1685 
1686 void
1687 Volume::MessageReceived(BMessage* message)
1688 {
1689 	switch (message->what) {
1690 		case B_NODE_MONITOR:
1691 		{
1692 			int32 opcode;
1693 			if (message->FindInt32("opcode", &opcode) != B_OK)
1694 				break;
1695 
1696 			switch (opcode) {
1697 				case B_ENTRY_CREATED:
1698 					_HandleEntryCreatedOrRemoved(message, true);
1699 					break;
1700 				case B_ENTRY_REMOVED:
1701 					_HandleEntryCreatedOrRemoved(message, false);
1702 					break;
1703 				case B_ENTRY_MOVED:
1704 					_HandleEntryMoved(message);
1705 					break;
1706 				default:
1707 					break;
1708 			}
1709 			break;
1710 		}
1711 
1712 		case kHandleNodeMonitorEvents:
1713 			if (fListener != NULL) {
1714 				if (system_time() >= fNodeMonitorEventHandleTime)
1715 					fListener->VolumeNodeMonitorEventOccurred(this);
1716 			}
1717 			break;
1718 
1719 		default:
1720 			BHandler::MessageReceived(message);
1721 			break;
1722 	}
1723 }
1724 
1725 
1726 BPackageInstallationLocation
1727 Volume::Location() const
1728 {
1729 	switch (fMountType) {
1730 		case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
1731 			return B_PACKAGE_INSTALLATION_LOCATION_SYSTEM;
1732 		case PACKAGE_FS_MOUNT_TYPE_HOME:
1733 			return B_PACKAGE_INSTALLATION_LOCATION_HOME;
1734 		case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
1735 		default:
1736 			return B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT;
1737 	}
1738 }
1739 
1740 
1741 int
1742 Volume::OpenRootDirectory() const
1743 {
1744 	BDirectory directory;
1745 	status_t error = directory.SetTo(&fRootDirectoryRef);
1746 	if (error != B_OK) {
1747 		ERROR("Volume::OpenRootDirectory(): failed to open root directory: "
1748 			"%s\n", strerror(error));
1749 		RETURN_ERROR(error);
1750 	}
1751 
1752 	return directory.Dup();
1753 }
1754 
1755 
1756 void
1757 Volume::ProcessPendingNodeMonitorEvents()
1758 {
1759 	// get the events
1760 	NodeMonitorEventList events;
1761 	{
1762 		AutoLocker<BLocker> eventsLock(fPendingNodeMonitorEventsLock);
1763 		events.MoveFrom(&fPendingNodeMonitorEvents);
1764 	}
1765 
1766 	// process them
1767 	while (NodeMonitorEvent* event = events.RemoveHead()) {
1768 		ObjectDeleter<NodeMonitorEvent> eventDeleter(event);
1769 		if (event->WasCreated())
1770 			_PackagesEntryCreated(event->EntryName());
1771 		else
1772 			_PackagesEntryRemoved(event->EntryName());
1773 	}
1774 }
1775 
1776 
1777 bool
1778 Volume::HasPendingPackageActivationChanges() const
1779 {
1780 	return !fPackagesToBeActivated.empty() || !fPackagesToBeDeactivated.empty();
1781 }
1782 
1783 
1784 void
1785 Volume::ProcessPendingPackageActivationChanges()
1786 {
1787 	if (!HasPendingPackageActivationChanges())
1788 		return;
1789 
1790 	// perform the request
1791 	CommitTransactionHandler handler(this, fPackagesToBeActivated,
1792 		fPackagesToBeDeactivated);
1793 	int32 error;
1794 	try {
1795 		handler.HandleRequest(fPackagesToBeActivated, fPackagesToBeDeactivated);
1796 		error = B_DAEMON_OK;
1797 	} catch (Exception& exception) {
1798 		error = exception.Error();
1799 		ERROR("Volume::ProcessPendingPackageActivationChanges(): package "
1800 			"activation failed: %s\n", exception.ToString().String());
1801 // TODO: Notify the user!
1802 	} catch (std::bad_alloc& exception) {
1803 		error = B_NO_MEMORY;
1804 		ERROR("Volume::ProcessPendingPackageActivationChanges(): package "
1805 			"activation failed: out of memory\n");
1806 // TODO: Notify the user!
1807 	}
1808 
1809 	// revert on error
1810 	if (error != B_DAEMON_OK)
1811 		handler.Revert();
1812 
1813 	// clear the activation/deactivation sets in any event
1814 	fPackagesToBeActivated.clear();
1815 	fPackagesToBeDeactivated.clear();
1816 }
1817 
1818 
1819 void
1820 Volume::ClearPackageActivationChanges()
1821 {
1822 	fPackagesToBeActivated.clear();
1823 	fPackagesToBeDeactivated.clear();
1824 }
1825 
1826 
1827 status_t
1828 Volume::CreateTransaction(BPackageInstallationLocation location,
1829 	BActivationTransaction& _transaction, BDirectory& _transactionDirectory)
1830 {
1831 	// open admin directory
1832 	BDirectory adminDirectory;
1833 	status_t error = _OpenPackagesSubDirectory(
1834 		RelativePath(kAdminDirectoryName), true, adminDirectory);
1835 	if (error != B_OK)
1836 		return error;
1837 
1838 	// create a transaction directory
1839 	int uniqueId = 1;
1840 	BString directoryName;
1841 	for (;; uniqueId++) {
1842 		directoryName.SetToFormat("transaction-%d", uniqueId);
1843 		if (directoryName.IsEmpty())
1844 			return B_NO_MEMORY;
1845 
1846 		error = adminDirectory.CreateDirectory(directoryName,
1847 			&_transactionDirectory);
1848 		if (error == B_OK)
1849 			break;
1850 		if (error != B_FILE_EXISTS)
1851 			return error;
1852 	}
1853 
1854 	// init the transaction
1855 	error = _transaction.SetTo(location, fChangeCount, directoryName);
1856 	if (error != B_OK) {
1857 		BEntry entry;
1858 		_transactionDirectory.GetEntry(&entry);
1859 		_transactionDirectory.Unset();
1860 		if (entry.InitCheck() == B_OK)
1861 			entry.Remove();
1862 		return error;
1863 	}
1864 
1865 	return B_OK;
1866 }
1867 
1868 
1869 void
1870 Volume::CommitTransaction(const BActivationTransaction& transaction,
1871 	const PackageSet& packagesAlreadyAdded,
1872 	const PackageSet& packagesAlreadyRemoved,
1873 	BDaemonClient::BCommitTransactionResult& _result)
1874 {
1875 	// perform the request
1876 	CommitTransactionHandler handler(this, packagesAlreadyAdded,
1877 		packagesAlreadyRemoved);
1878 	int32 error;
1879 	try {
1880 		handler.HandleRequest(transaction, NULL);
1881 		error = B_DAEMON_OK;
1882 		_result.SetTo(error, BString(), BString(),
1883 			handler.OldStateDirectoryName());
1884 	} catch (Exception& exception) {
1885 		error = exception.Error();
1886 		_result.SetTo(error, exception.ErrorMessage(), exception.PackageName(),
1887 			BString());
1888 	} catch (std::bad_alloc& exception) {
1889 		error = B_NO_MEMORY;
1890 		_result.SetTo(error, BString(), BString(), BString());
1891 	}
1892 
1893 	// revert on error
1894 	if (error != B_DAEMON_OK)
1895 		handler.Revert();
1896 }
1897 
1898 
1899 void
1900 Volume::_HandleEntryCreatedOrRemoved(const BMessage* message, bool created)
1901 {
1902 	// only moves to or from our packages directory are interesting
1903 	int32 deviceID;
1904 	int64 directoryID;
1905 	const char* name;
1906 	if (message->FindInt32("device", &deviceID) != B_OK
1907 		|| message->FindInt64("directory", &directoryID) != B_OK
1908 		|| message->FindString("name", &name) != B_OK
1909 		|| node_ref(deviceID, directoryID) != fPackagesDirectoryRef) {
1910 		return;
1911 	}
1912 
1913 	_QueueNodeMonitorEvent(name, created);
1914 }
1915 
1916 
1917 void
1918 Volume::_HandleEntryMoved(const BMessage* message)
1919 {
1920 	int32 deviceID;
1921 	int64 fromDirectoryID;
1922 	int64 toDirectoryID;
1923 	const char* fromName;
1924 	const char* toName;
1925 	if (message->FindInt32("device", &deviceID) != B_OK
1926 		|| message->FindInt64("from directory", &fromDirectoryID) != B_OK
1927 		|| message->FindInt64("to directory", &toDirectoryID) != B_OK
1928 		|| message->FindString("from name", &fromName) != B_OK
1929 		|| message->FindString("name", &toName) != B_OK
1930 		|| deviceID != fPackagesDirectoryRef.device
1931 		|| (fromDirectoryID != fPackagesDirectoryRef.node
1932 			&& toDirectoryID != fPackagesDirectoryRef.node)) {
1933 		return;
1934 	}
1935 
1936 	AutoLocker<BLocker> eventsLock(fPendingNodeMonitorEventsLock);
1937 		// make sure for a move the two events cannot get split
1938 
1939 	if (fromDirectoryID == fPackagesDirectoryRef.node)
1940 		_QueueNodeMonitorEvent(fromName, false);
1941 	if (toDirectoryID == fPackagesDirectoryRef.node)
1942 		_QueueNodeMonitorEvent(toName, true);
1943 }
1944 
1945 
1946 void
1947 Volume::_QueueNodeMonitorEvent(const BString& name, bool wasCreated)
1948 {
1949 	if (name.IsEmpty()) {
1950 		ERROR("Volume::_QueueNodeMonitorEvent(): got empty name.\n");
1951 		return;
1952 	}
1953 
1954 	// ignore entries that don't have the ".hpkg" extension
1955 	if (!name.EndsWith(kPackageFileNameExtension))
1956 		return;
1957 
1958 	NodeMonitorEvent* event
1959 		= new(std::nothrow) NodeMonitorEvent(name, wasCreated);
1960 	if (event == NULL) {
1961 		ERROR("Volume::_QueueNodeMonitorEvent(): out of memory.\n");
1962 		return;
1963 	}
1964 
1965 	AutoLocker<BLocker> eventsLock(fPendingNodeMonitorEventsLock);
1966 	fPendingNodeMonitorEvents.Add(event);
1967 	eventsLock.Unlock();
1968 
1969 	fNodeMonitorEventHandleTime
1970 		= system_time() + kNodeMonitorEventHandlingDelay;
1971 	BMessage message(kHandleNodeMonitorEvents);
1972 	BMessageRunner::StartSending(this, &message, kNodeMonitorEventHandlingDelay,
1973 		1);
1974 }
1975 
1976 
1977 void
1978 Volume::_PackagesEntryCreated(const char* name)
1979 {
1980 INFORM("Volume::_PackagesEntryCreated(\"%s\")\n", name);
1981 	// Ignore the event, if the package is already known.
1982 	Package* package = fPackagesByFileName.Lookup(name);
1983 	if (package != NULL) {
1984 		if (package->EntryCreatedIgnoreLevel() > 0) {
1985 			package->DecrementEntryCreatedIgnoreLevel();
1986 		} else {
1987 			WARN("node monitoring created event for already known entry "
1988 				"\"%s\"\n", name);
1989 		}
1990 
1991 		return;
1992 	}
1993 
1994 	entry_ref entry;
1995 	entry.device = fPackagesDirectoryRef.device;
1996 	entry.directory = fPackagesDirectoryRef.node;
1997 	status_t error = entry.set_name(name);
1998 	if (error != B_OK) {
1999 		ERROR("out of memory\n");
2000 		return;
2001 	}
2002 
2003 	package = new(std::nothrow) Package;
2004 	if (package == NULL) {
2005 		ERROR("out of memory\n");
2006 		return;
2007 	}
2008 	ObjectDeleter<Package> packageDeleter(package);
2009 
2010 	error = package->Init(entry);
2011 	if (error != B_OK) {
2012 		ERROR("failed to init package for file \"%s\"\n", name);
2013 		return;
2014 	}
2015 
2016 	_AddPackage(package);
2017 	packageDeleter.Detach();
2018 
2019 	try {
2020 		fPackagesToBeActivated.insert(package);
2021 	} catch (std::bad_alloc& exception) {
2022 		ERROR("out of memory\n");
2023 		return;
2024 	}
2025 }
2026 
2027 
2028 void
2029 Volume::_PackagesEntryRemoved(const char* name)
2030 {
2031 INFORM("Volume::_PackagesEntryRemoved(\"%s\")\n", name);
2032 	Package* package = fPackagesByFileName.Lookup(name);
2033 	if (package == NULL)
2034 		return;
2035 
2036 	// Ignore the event, if we generated it ourselves.
2037 	if (package->EntryRemovedIgnoreLevel() > 0) {
2038 		package->DecrementEntryRemovedIgnoreLevel();
2039 		return;
2040 	}
2041 
2042 	// Remove the package from the packages-to-be-activated set, if it is in
2043 	// there (unlikely, unless we see a create-remove-create sequence).
2044 	PackageSet::iterator it = fPackagesToBeActivated.find(package);
2045 	if (it != fPackagesToBeActivated.end())
2046 		fPackagesToBeActivated.erase(it);
2047 
2048 	// If the package isn't active, just remove it for good.
2049 	if (!package->IsActive()) {
2050 		_RemovePackage(package);
2051 		delete package;
2052 		return;
2053 	}
2054 
2055 	// The package must be deactivated.
2056 	try {
2057 		fPackagesToBeDeactivated.insert(package);
2058 	} catch (std::bad_alloc& exception) {
2059 		ERROR("out of memory\n");
2060 		return;
2061 	}
2062 }
2063 
2064 
2065 void
2066 Volume::_FillInActivationChangeItem(PackageFSActivationChangeItem* item,
2067 	PackageFSActivationChangeType type, Package* package, char*& nameBuffer)
2068 {
2069 	item->type = type;
2070 	item->packageDeviceID = package->NodeRef().device;
2071 	item->packageNodeID = package->NodeRef().node;
2072 	item->nameLength = package->FileName().Length();
2073 	item->parentDeviceID = fPackagesDirectoryRef.device;
2074 	item->parentDirectoryID = fPackagesDirectoryRef.node;
2075 	item->name = nameBuffer;
2076 	strcpy(nameBuffer, package->FileName());
2077 	nameBuffer += package->FileName().Length() + 1;
2078 }
2079 
2080 
2081 void
2082 Volume::_AddPackage(Package* package)
2083 {
2084 	fPackagesByFileName.Insert(package);
2085 	fPackagesByNodeRef.Insert(package);
2086 }
2087 
2088 void
2089 Volume::_RemovePackage(Package* package)
2090 {
2091 	fPackagesByFileName.Remove(package);
2092 	fPackagesByNodeRef.Remove(package);
2093 	fChangeCount++;
2094 }
2095 
2096 
2097 status_t
2098 Volume::_ReadPackagesDirectory()
2099 {
2100 	BDirectory directory;
2101 	status_t error = directory.SetTo(&fPackagesDirectoryRef);
2102 	if (error != B_OK) {
2103 		ERROR("Volume::_ReadPackagesDirectory(): failed to open packages "
2104 			"directory: %s\n", strerror(error));
2105 		RETURN_ERROR(error);
2106 	}
2107 
2108 	entry_ref entry;
2109 	while (directory.GetNextRef(&entry) == B_OK) {
2110 		if (!BString(entry.name).EndsWith(kPackageFileNameExtension))
2111 			continue;
2112 
2113 		Package* package = new(std::nothrow) Package;
2114 		if (package == NULL)
2115 			RETURN_ERROR(B_NO_MEMORY);
2116 		ObjectDeleter<Package> packageDeleter(package);
2117 
2118 		status_t error = package->Init(entry);
2119 		if (error == B_OK) {
2120 			_AddPackage(package);
2121 			packageDeleter.Detach();
2122 		}
2123 	}
2124 
2125 	return B_OK;
2126 }
2127 
2128 
2129 status_t
2130 Volume::_GetActivePackages(int fd)
2131 {
2132 	uint32 maxPackageCount = 16 * 1024;
2133 	PackageFSGetPackageInfosRequest* request = NULL;
2134 	MemoryDeleter requestDeleter;
2135 	size_t bufferSize;
2136 	for (;;) {
2137 		bufferSize = sizeof(PackageFSGetPackageInfosRequest)
2138 			+ (maxPackageCount - 1) * sizeof(PackageFSPackageInfo);
2139 		request = (PackageFSGetPackageInfosRequest*)malloc(bufferSize);
2140 		if (request == NULL)
2141 			RETURN_ERROR(B_NO_MEMORY);
2142 		requestDeleter.SetTo(request);
2143 
2144 		if (ioctl(fd, PACKAGE_FS_OPERATION_GET_PACKAGE_INFOS, request,
2145 				bufferSize) != 0) {
2146 			ERROR("Volume::_GetActivePackages(): failed to get active package "
2147 				"info from package FS: %s\n", strerror(errno));
2148 			RETURN_ERROR(errno);
2149 		}
2150 
2151 		if (request->packageCount <= maxPackageCount)
2152 			break;
2153 
2154 		maxPackageCount = request->packageCount;
2155 		requestDeleter.Unset();
2156 	}
2157 
2158 	// mark the returned packages active
2159 	for (uint32 i = 0; i < request->packageCount; i++) {
2160 		Package* package = fPackagesByNodeRef.Lookup(
2161 			node_ref(request->infos[i].packageDeviceID,
2162 				request->infos[i].packageNodeID));
2163 		if (package == NULL) {
2164 			WARN("active package (dev: %" B_PRIdDEV ", node: %" B_PRIdINO ") "
2165 				"not found in package directory\n",
2166 				request->infos[i].packageDeviceID,
2167 				request->infos[i].packageNodeID);
2168 // TODO: Deactivate the package right away?
2169 			continue;
2170 		}
2171 
2172 		package->SetActive(true);
2173 INFORM("active package: \"%s\"\n", package->FileName().String());
2174 	}
2175 
2176 for (PackageNodeRefHashTable::Iterator it = fPackagesByNodeRef.GetIterator();
2177 	it.HasNext();) {
2178 	Package* package = it.Next();
2179 	if (!package->IsActive())
2180 		INFORM("inactive package: \"%s\"\n", package->FileName().String());
2181 }
2182 
2183 			PackageNodeRefHashTable fPackagesByNodeRef;
2184 // INFORM("%" B_PRIu32 " active packages:\n", request->packageCount);
2185 // for (uint32 i = 0; i < request->packageCount; i++) {
2186 // 	INFORM("  dev: %" B_PRIdDEV ", node: %" B_PRIdINO "\n",
2187 // 		request->infos[i].packageDeviceID, request->infos[i].packageNodeID);
2188 // }
2189 
2190 	return B_OK;
2191 }
2192 
2193 
2194 status_t
2195 Volume::_AddRepository(BSolver* solver, BSolverRepository& repository,
2196 	bool activeOnly, bool installed)
2197 {
2198 	status_t error = repository.SetTo(Path());
2199 	if (error != B_OK) {
2200 		ERROR("Volume::_AddRepository(): failed to init repository: %s\n",
2201 			strerror(error));
2202 		return error;
2203 	}
2204 
2205 	repository.SetInstalled(installed);
2206 
2207 	error = AddPackagesToRepository(repository, true);
2208 	if (error != B_OK) {
2209 		ERROR("Volume::_AddRepository(): failed to add packages to "
2210 			"repository: %s\n", strerror(error));
2211 		return error;
2212 	}
2213 
2214 	error = solver->AddRepository(&repository);
2215 	if (error != B_OK) {
2216 		ERROR("Volume::_AddRepository(): failed to add repository to solver: "
2217 			"%s\n", strerror(error));
2218 		return error;
2219 	}
2220 
2221 	return B_OK;
2222 }
2223 
2224 
2225 status_t
2226 Volume::_OpenPackagesFile(const RelativePath& subDirectoryPath,
2227 	const char* fileName, uint32 openMode, BFile& _file, BEntry* _entry)
2228 {
2229 	BDirectory directory;
2230 	if (!subDirectoryPath.IsEmpty()) {
2231 		status_t error = _OpenPackagesSubDirectory(subDirectoryPath,
2232 			(openMode & B_CREATE_FILE) != 0, directory);
2233 		if (error != B_OK) {
2234 			ERROR("Volume::_OpenPackagesFile(): failed to open packages "
2235 				"subdirectory \"%s\": %s\n",
2236 				subDirectoryPath.ToString().String(), strerror(error));
2237 			RETURN_ERROR(error);
2238 		}
2239 	} else {
2240 		status_t error = directory.SetTo(&fPackagesDirectoryRef);
2241 		if (error != B_OK) {
2242 			ERROR("Volume::_OpenPackagesFile(): failed to open packages "
2243 				"directory: %s\n", strerror(error));
2244 			RETURN_ERROR(error);
2245 		}
2246 	}
2247 
2248 	BEntry stackEntry;
2249 	BEntry& entry = _entry != NULL ? *_entry : stackEntry;
2250 	status_t error = entry.SetTo(&directory, fileName);
2251 	if (error != B_OK) {
2252 		ERROR("Volume::_OpenPackagesFile(): failed to get entry for file: %s",
2253 			strerror(error));
2254 		RETURN_ERROR(error);
2255 	}
2256 
2257 	return _file.SetTo(&entry, openMode);
2258 }
2259 
2260 
2261 status_t
2262 Volume::_OpenPackagesSubDirectory(const RelativePath& path, bool create,
2263 	BDirectory& _directory)
2264 {
2265 	// open the packages directory
2266 	BDirectory directory;
2267 	status_t error = directory.SetTo(&fPackagesDirectoryRef);
2268 	if (error != B_OK) {
2269 		ERROR("Volume::_OpenPackagesSubDirectory(): failed to open packages "
2270 			"directory: %s\n", strerror(error));
2271 		RETURN_ERROR(error);
2272 	}
2273 
2274 	return FSUtils::OpenSubDirectory(directory, path, create, _directory);
2275 }
2276 
2277 
2278 status_t
2279 Volume::_CreateActivationFileContent(const PackageSet& toActivate,
2280 	const PackageSet& toDeactivate, BString& _content)
2281 {
2282 	BString activationFileContent;
2283 	for (PackageFileNameHashTable::Iterator it
2284 			= fPackagesByFileName.GetIterator(); it.HasNext();) {
2285 		Package* package = it.Next();
2286 		if (package->IsActive()
2287 			&& toDeactivate.find(package) == toDeactivate.end()) {
2288 			int32 length = activationFileContent.Length();
2289 			activationFileContent << package->FileName() << '\n';
2290 			if (activationFileContent.Length()
2291 					< length + package->FileName().Length() + 1) {
2292 				return B_NO_MEMORY;
2293 			}
2294 		}
2295 	}
2296 
2297 	for (PackageSet::const_iterator it = toActivate.begin();
2298 		it != toActivate.end(); ++it) {
2299 		Package* package = *it;
2300 		int32 length = activationFileContent.Length();
2301 		activationFileContent << package->FileName() << '\n';
2302 		if (activationFileContent.Length()
2303 				< length + package->FileName().Length() + 1) {
2304 			return B_NO_MEMORY;
2305 		}
2306 	}
2307 
2308 	_content = activationFileContent;
2309 	return B_OK;
2310 }
2311 
2312 
2313 status_t
2314 Volume::_WriteActivationFile(const RelativePath& directoryPath,
2315 	const char* fileName, const PackageSet& toActivate,
2316 	const PackageSet& toDeactivate,
2317 	BEntry& _entry)
2318 {
2319 	// create the content
2320 	BString activationFileContent;
2321 	status_t error = _CreateActivationFileContent(toActivate, toDeactivate,
2322 		activationFileContent);
2323 	if (error != B_OK)
2324 		return error;
2325 
2326 	// write the file
2327 	error = _WriteTextFile(directoryPath, fileName, activationFileContent,
2328 		_entry);
2329 	if (error != B_OK) {
2330 		ERROR("Volume::_WriteActivationFile(): failed to write activation "
2331 			"file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName,
2332 			strerror(error));
2333 		return error;
2334 	}
2335 
2336 	return B_OK;
2337 }
2338 
2339 
2340 status_t
2341 Volume::_WriteTextFile(const RelativePath& directoryPath, const char* fileName,
2342 	const BString& content, BEntry& _entry)
2343 {
2344 	BFile file;
2345 	status_t error = _OpenPackagesFile(directoryPath,
2346 		fileName, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE, file, &_entry);
2347 	if (error != B_OK) {
2348 		ERROR("Volume::_WriteTextFile(): failed to create file \"%s/%s\": %s\n",
2349 			directoryPath.ToString().String(), fileName, strerror(error));
2350 		return error;
2351 	}
2352 
2353 	ssize_t bytesWritten = file.Write(content.String(),
2354 		content.Length());
2355 	if (bytesWritten < 0) {
2356 		ERROR("Volume::_WriteTextFile(): failed to write file \"%s/%s\": %s\n",
2357 			directoryPath.ToString().String(), fileName,
2358 			strerror(bytesWritten));
2359 		return bytesWritten;
2360 	}
2361 
2362 	return B_OK;
2363 }
2364 
2365 
2366 void
2367 Volume::_ChangePackageActivation(const PackageSet& packagesToActivate,
2368 	const PackageSet& packagesToDeactivate)
2369 {
2370 INFORM("Volume::_ChangePackageActivation(): activating %zu, deactivating %zu packages\n",
2371 packagesToActivate.size(), packagesToDeactivate.size());
2372 
2373 	// write the temporary package activation file
2374 	BEntry activationFileEntry;
2375 	status_t error = _WriteActivationFile(RelativePath(kAdminDirectoryName),
2376 		kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate,
2377 		activationFileEntry);
2378 	if (error != B_OK)
2379 		throw Exception(error, "failed to write activation file");
2380 
2381 	// compute the size of the allocation we need for the activation change
2382 	// request
2383 	int32 itemCount = packagesToActivate.size() + packagesToDeactivate.size();
2384 	size_t requestSize = sizeof(PackageFSActivationChangeRequest)
2385 		+ itemCount * sizeof(PackageFSActivationChangeItem);
2386 
2387 	for (PackageSet::iterator it = packagesToActivate.begin();
2388 		 it != packagesToActivate.end(); ++it) {
2389 		requestSize += (*it)->FileName().Length() + 1;
2390 	}
2391 
2392 	for (PackageSet::iterator it = packagesToDeactivate.begin();
2393 		 it != packagesToDeactivate.end(); ++it) {
2394 		requestSize += (*it)->FileName().Length() + 1;
2395 	}
2396 
2397 	// allocate and prepare the request
2398 	PackageFSActivationChangeRequest* request
2399 		= (PackageFSActivationChangeRequest*)malloc(requestSize);
2400 	if (request == NULL)
2401 		throw Exception(B_NO_MEMORY);
2402 	MemoryDeleter requestDeleter(request);
2403 
2404 	request->itemCount = itemCount;
2405 
2406 	PackageFSActivationChangeItem* item = &request->items[0];
2407 	char* nameBuffer = (char*)(item + itemCount);
2408 
2409 	for (PackageSet::iterator it = packagesToActivate.begin();
2410 		it != packagesToActivate.end(); ++it, item++) {
2411 		_FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it,
2412 			nameBuffer);
2413 	}
2414 
2415 	for (PackageSet::iterator it = packagesToDeactivate.begin();
2416 		it != packagesToDeactivate.end(); ++it, item++) {
2417 		_FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it,
2418 			nameBuffer);
2419 	}
2420 
2421 	// issue the request
2422 	int fd = OpenRootDirectory();
2423 	if (fd < 0)
2424 		throw Exception(fd, "failed to open root directory");
2425 	FileDescriptorCloser fdCloser(fd);
2426 
2427 	if (ioctl(fd, PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request, requestSize)
2428 			!= 0) {
2429 // TODO: We need more error information and error handling!
2430 		throw Exception(errno, "ioctl() to de-/activate packages failed");
2431 	}
2432 
2433 	// rename the temporary activation file to the final file
2434 	error = activationFileEntry.Rename(kActivationFileName, true);
2435 	if (error != B_OK) {
2436 		throw Exception(error,
2437 			"failed to rename temporary activation file to final file");
2438 // TODO: We should probably try to reverse the activation changes, though that
2439 // will fail, if this method has been called in response to node monitoring
2440 // events. Alternatively moving the package activation file could be made part
2441 // of the ioctl(), since packagefs should be able to undo package changes until
2442 // the very end, unless running out of memory. In the end the situation would be
2443 // bad anyway, though, since the activation file may refer to removed packages
2444 // and things would be in an inconsistent state after rebooting.
2445 	}
2446 
2447 	// Update our state, i.e. remove deactivated packages and mark activated
2448 	// packages accordingly.
2449 	for (PackageSet::iterator it = packagesToActivate.begin();
2450 		it != packagesToActivate.end(); ++it) {
2451 		(*it)->SetActive(true);
2452 		fChangeCount++;
2453 	}
2454 
2455 	for (PackageSet::iterator it = packagesToDeactivate.begin();
2456 		it != packagesToDeactivate.end(); ++it) {
2457 		Package* package = *it;
2458 		_RemovePackage(package);
2459 		delete package;
2460 	}
2461 }
2462