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