xref: /haiku/src/kits/package/manager/PackageManager.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
1 /*
2  * Copyright 2013-2020, 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  *		Rene Gollent <rene@gollent.com>
8  */
9 
10 
11 #include <package/manager/PackageManager.h>
12 
13 #include <glob.h>
14 
15 #include <Catalog.h>
16 #include <Directory.h>
17 #include <package/CommitTransactionResult.h>
18 #include <package/DownloadFileRequest.h>
19 #include <package/PackageRoster.h>
20 #include <package/RefreshRepositoryRequest.h>
21 #include <package/RepositoryCache.h>
22 #include <package/solver/SolverPackage.h>
23 #include <package/solver/SolverPackageSpecifier.h>
24 #include <package/solver/SolverPackageSpecifierList.h>
25 #include <package/solver/SolverProblem.h>
26 #include <package/solver/SolverProblemSolution.h>
27 #include <package/solver/SolverResult.h>
28 
29 #include <CopyEngine.h>
30 #include <package/ActivationTransaction.h>
31 #include <package/DaemonClient.h>
32 #include <package/manager/RepositoryBuilder.h>
33 #include <package/ValidateChecksumJob.h>
34 
35 #include "FetchFileJob.h"
36 #include "FetchUtils.h"
37 using BPackageKit::BPrivate::FetchUtils;
38 #include "PackageManagerUtils.h"
39 
40 #undef B_TRANSLATION_CONTEXT
41 #define B_TRANSLATION_CONTEXT "PackageManagerKit"
42 
43 
44 using BPackageKit::BPrivate::FetchFileJob;
45 using BPackageKit::BPrivate::ValidateChecksumJob;
46 
47 
48 namespace BPackageKit {
49 
50 namespace BManager {
51 
52 namespace BPrivate {
53 
54 
55 // #pragma mark - BPackageManager
56 
57 
58 BPackageManager::BPackageManager(BPackageInstallationLocation location,
59 	InstallationInterface* installationInterface,
60 	UserInteractionHandler* userInteractionHandler)
61 	:
62 	fDebugLevel(0),
63 	fLocation(location),
64 	fSolver(NULL),
65 	fSystemRepository(new (std::nothrow) InstalledRepository("system",
66 		B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, -1)),
67 	fHomeRepository(new (std::nothrow) InstalledRepository("home",
68 		B_PACKAGE_INSTALLATION_LOCATION_HOME, -3)),
69 	fInstalledRepositories(10),
70 	fOtherRepositories(10, true),
71 	fLocalRepository(new (std::nothrow) MiscLocalRepository),
72 	fTransactions(5, true),
73 	fInstallationInterface(installationInterface),
74 	fUserInteractionHandler(userInteractionHandler)
75 {
76 }
77 
78 
79 BPackageManager::~BPackageManager()
80 {
81 	delete fSolver;
82 	delete fSystemRepository;
83 	delete fHomeRepository;
84 	delete fLocalRepository;
85 }
86 
87 
88 void
89 BPackageManager::Init(uint32 flags)
90 {
91 	if (fSolver != NULL)
92 		return;
93 
94 	// create the solver
95 	status_t error = BSolver::Create(fSolver);
96 	if (error != B_OK)
97 		DIE(error, "Failed to create solver");
98 
99 	if (fSystemRepository == NULL || fHomeRepository == NULL
100 		|| fLocalRepository == NULL) {
101 		throw std::bad_alloc();
102 	}
103 
104 	fSolver->SetDebugLevel(fDebugLevel);
105 
106 	BRepositoryBuilder(*fLocalRepository).AddToSolver(fSolver, false);
107 
108 	// add installation location repositories
109 	if ((flags & B_ADD_INSTALLED_REPOSITORIES) != 0) {
110 		// We add only the repository of our actual installation location as the
111 		// "installed" repository. The repositories for the more general
112 		// installation locations are added as regular repositories, but with
113 		// better priorities than the actual (remote) repositories. This
114 		// prevents the solver from showing conflicts when a package in a more
115 		// specific installation location overrides a package in a more general
116 		// one. Instead any requirement that is already installed in a more
117 		// general installation location will turn up as to be installed as
118 		// well. But we can easily filter those out.
119 		_AddInstalledRepository(fSystemRepository);
120 
121 		if (!fSystemRepository->IsInstalled()) {
122 			// Only add the home repository if the directory exists
123 			BPath path;
124 			status_t error = find_directory(B_USER_PACKAGES_DIRECTORY, &path);
125 			if (error == B_OK && BEntry(path.Path()).Exists())
126 				_AddInstalledRepository(fHomeRepository);
127 		}
128 	}
129 
130 	// add other repositories
131 	if ((flags & B_ADD_REMOTE_REPOSITORIES) != 0) {
132 		BPackageRoster roster;
133 		BStringList repositoryNames;
134 		error = roster.GetRepositoryNames(repositoryNames);
135 		if (error != B_OK) {
136 			fUserInteractionHandler->Warn(error,
137 				B_TRANSLATE("Failed to get repository names"));
138 		}
139 
140 		int32 repositoryNameCount = repositoryNames.CountStrings();
141 		for (int32 i = 0; i < repositoryNameCount; i++) {
142 			_AddRemoteRepository(roster, repositoryNames.StringAt(i),
143 				(flags & B_REFRESH_REPOSITORIES) != 0);
144 		}
145 	}
146 }
147 
148 
149 void
150 BPackageManager::SetDebugLevel(int32 level)
151 {
152 	fDebugLevel = level;
153 
154 	if (fSolver != NULL)
155 		fSolver->SetDebugLevel(fDebugLevel);
156 }
157 
158 
159 void
160 BPackageManager::Install(const char* const* packages, int packageCount)
161 {
162 	BSolverPackageSpecifierList packagesToInstall;
163 	_AddPackageSpecifiers(packages, packageCount, packagesToInstall);
164 	Install(packagesToInstall);
165 }
166 
167 
168 void
169 BPackageManager::Install(const BSolverPackageSpecifierList& packages)
170 {
171 	Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
172 		| B_REFRESH_REPOSITORIES);
173 
174 	// solve
175 	const BSolverPackageSpecifier* unmatchedSpecifier;
176 	status_t error = fSolver->Install(packages, &unmatchedSpecifier);
177 	if (error != B_OK) {
178 		if (unmatchedSpecifier != NULL) {
179 			DIE(error, "Failed to find a match for \"%s\"",
180 				unmatchedSpecifier->SelectString().String());
181 		} else
182 			DIE(error, "Failed to compute packages to install");
183 	}
184 
185 	_HandleProblems();
186 
187 	// install/uninstall packages
188 	_AnalyzeResult();
189 	_ConfirmChanges();
190 	_ApplyPackageChanges();
191 }
192 
193 
194 void
195 BPackageManager::Uninstall(const char* const* packages, int packageCount)
196 {
197 	BSolverPackageSpecifierList packagesToUninstall;
198 	if (!packagesToUninstall.AppendSpecifiers(packages, packageCount))
199 		throw std::bad_alloc();
200 	Uninstall(packagesToUninstall);
201 }
202 
203 
204 void
205 BPackageManager::Uninstall(const BSolverPackageSpecifierList& packages)
206 {
207 	Init(B_ADD_INSTALLED_REPOSITORIES);
208 
209 	// find the packages that match the specification
210 	const BSolverPackageSpecifier* unmatchedSpecifier;
211 	PackageList foundPackages;
212 	status_t error = fSolver->FindPackages(packages,
213 		BSolver::B_FIND_INSTALLED_ONLY, foundPackages, &unmatchedSpecifier);
214 	if (error != B_OK) {
215 		if (unmatchedSpecifier != NULL) {
216 			DIE(error, "Failed to find a match for \"%s\"",
217 				unmatchedSpecifier->SelectString().String());
218 		} else
219 			DIE(error, "Failed to compute packages to uninstall");
220 	}
221 
222 	// determine the inverse base package closure for the found packages
223 // TODO: Optimize!
224 	InstalledRepository& installationRepository = InstallationRepository();
225 	bool foundAnotherPackage;
226 	do {
227 		foundAnotherPackage = false;
228 		int32 count = installationRepository.CountPackages();
229 		for (int32 i = 0; i < count; i++) {
230 			BSolverPackage* package = installationRepository.PackageAt(i);
231 			if (foundPackages.HasItem(package))
232 				continue;
233 
234 			if (_FindBasePackage(foundPackages, package->Info()) >= 0) {
235 				foundPackages.AddItem(package);
236 				foundAnotherPackage = true;
237 			}
238 		}
239 	} while (foundAnotherPackage);
240 
241 	// remove the packages from the repository
242 	for (int32 i = 0; BSolverPackage* package = foundPackages.ItemAt(i); i++)
243 		installationRepository.DisablePackage(package);
244 
245 	for (;;) {
246 		error = fSolver->VerifyInstallation(BSolver::B_VERIFY_ALLOW_UNINSTALL);
247 		if (error != B_OK)
248 			DIE(error, "Failed to compute packages to uninstall");
249 
250 		_HandleProblems();
251 
252 		// (virtually) apply the result to this repository
253 		_AnalyzeResult();
254 
255 		for (int32 i = foundPackages.CountItems() - 1; i >= 0; i--) {
256 			if (!installationRepository.PackagesToDeactivate()
257 					.AddItem(foundPackages.ItemAt(i))) {
258 				throw std::bad_alloc();
259 			}
260 		}
261 
262 		installationRepository.ApplyChanges();
263 
264 		// verify the next specific respository
265 		if (!_NextSpecificInstallationLocation())
266 			break;
267 
268 		foundPackages.MakeEmpty();
269 
270 		// NOTE: In theory, after verifying a more specific location, it would
271 		// be more correct to compute the inverse base package closure for the
272 		// packages we need to uninstall and (if anything changed) verify again.
273 		// In practice, however, base packages are always required with an exact
274 		// version (ATM). If that base package still exist in a more general
275 		// location (the only reason why the package requiring the base package
276 		// wouldn't be marked to be uninstalled as well) there shouldn't have
277 		// been any reason to remove it from the more specific location in the
278 		// first place.
279 	}
280 
281 	_ConfirmChanges(true);
282 	_ApplyPackageChanges(true);
283 }
284 
285 
286 void
287 BPackageManager::Update(const char* const* packages, int packageCount)
288 {
289 	BSolverPackageSpecifierList packagesToUpdate;
290 	_AddPackageSpecifiers(packages, packageCount, packagesToUpdate);
291 	Update(packagesToUpdate);
292 }
293 
294 
295 void
296 BPackageManager::Update(const BSolverPackageSpecifierList& packages)
297 {
298 	Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
299 		| B_REFRESH_REPOSITORIES);
300 
301 	// solve
302 	const BSolverPackageSpecifier* unmatchedSpecifier;
303 	status_t error = fSolver->Update(packages, true,
304 		&unmatchedSpecifier);
305 	if (error != B_OK) {
306 		if (unmatchedSpecifier != NULL) {
307 			DIE(error, "Failed to find a match for \"%s\"",
308 				unmatchedSpecifier->SelectString().String());
309 		} else
310 			DIE(error, "Failed to compute packages to update");
311 	}
312 
313 	_HandleProblems();
314 
315 	// install/uninstall packages
316 	_AnalyzeResult();
317 	_ConfirmChanges();
318 	_ApplyPackageChanges();
319 }
320 
321 
322 void
323 BPackageManager::FullSync()
324 {
325 	Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
326 		| B_REFRESH_REPOSITORIES);
327 
328 	// solve
329 	status_t error = fSolver->FullSync();
330 	if (error != B_OK)
331 		DIE(error, "Failed to compute packages to synchronize");
332 
333 	_HandleProblems();
334 
335 	// install/uninstall packages
336 	_AnalyzeResult();
337 	_ConfirmChanges();
338 	_ApplyPackageChanges();
339 }
340 
341 
342 void
343 BPackageManager::VerifyInstallation()
344 {
345 	Init(B_ADD_INSTALLED_REPOSITORIES | B_ADD_REMOTE_REPOSITORIES
346 		| B_REFRESH_REPOSITORIES);
347 
348 	for (;;) {
349 		status_t error = fSolver->VerifyInstallation();
350 		if (error != B_OK)
351 			DIE(error, "Failed to compute package dependencies");
352 
353 		_HandleProblems();
354 
355 		// (virtually) apply the result to this repository
356 		_AnalyzeResult();
357 		InstallationRepository().ApplyChanges();
358 
359 		// verify the next specific respository
360 		if (!_NextSpecificInstallationLocation())
361 			break;
362 	}
363 
364 	_ConfirmChanges();
365 	_ApplyPackageChanges();
366 }
367 
368 
369 BPackageManager::InstalledRepository&
370 BPackageManager::InstallationRepository()
371 {
372 	if (fInstalledRepositories.IsEmpty())
373 		DIE("No installation repository");
374 
375 	return *fInstalledRepositories.LastItem();
376 }
377 
378 
379 void
380 BPackageManager::JobStarted(BSupportKit::BJob* job)
381 {
382 	if (dynamic_cast<FetchFileJob*>(job) != NULL) {
383 		FetchFileJob* fetchJob = (FetchFileJob*)job;
384 		fUserInteractionHandler->ProgressPackageDownloadStarted(
385 			fetchJob->DownloadFileName());
386 	} else if (dynamic_cast<ValidateChecksumJob*>(job) != NULL) {
387 		fUserInteractionHandler->ProgressPackageChecksumStarted(
388 			job->Title().String());
389 	}
390 }
391 
392 
393 void
394 BPackageManager::JobProgress(BSupportKit::BJob* job)
395 {
396 	if (dynamic_cast<FetchFileJob*>(job) != NULL) {
397 		FetchFileJob* fetchJob = (FetchFileJob*)job;
398 		fUserInteractionHandler->ProgressPackageDownloadActive(
399 			fetchJob->DownloadFileName(), fetchJob->DownloadProgress(),
400 			fetchJob->DownloadBytes(), fetchJob->DownloadTotalBytes());
401 	}
402 }
403 
404 
405 void
406 BPackageManager::JobSucceeded(BSupportKit::BJob* job)
407 {
408 	if (dynamic_cast<FetchFileJob*>(job) != NULL) {
409 		FetchFileJob* fetchJob = (FetchFileJob*)job;
410 		fUserInteractionHandler->ProgressPackageDownloadComplete(
411 			fetchJob->DownloadFileName());
412 	} else if (dynamic_cast<ValidateChecksumJob*>(job) != NULL) {
413 		fUserInteractionHandler->ProgressPackageChecksumComplete(
414 			job->Title().String());
415 	}
416 }
417 
418 
419 void
420 BPackageManager::_HandleProblems()
421 {
422 	while (fSolver->HasProblems()) {
423 		fUserInteractionHandler->HandleProblems();
424 
425 		status_t error = fSolver->SolveAgain();
426 		if (error != B_OK)
427 			DIE(error, "Failed to recompute packages to un/-install");
428 	}
429 }
430 
431 
432 void
433 BPackageManager::_AnalyzeResult()
434 {
435 	BSolverResult result;
436 	status_t error = fSolver->GetResult(result);
437 	if (error != B_OK)
438 		DIE(error, "Failed to compute packages to un/-install");
439 
440 	InstalledRepository& installationRepository = InstallationRepository();
441 	PackageList& packagesToActivate
442 		= installationRepository.PackagesToActivate();
443 	PackageList& packagesToDeactivate
444 		= installationRepository.PackagesToDeactivate();
445 
446 	PackageList potentialBasePackages;
447 
448 	for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
449 			i++) {
450 		BSolverPackage* package = element->Package();
451 
452 		switch (element->Type()) {
453 			case BSolverResultElement::B_TYPE_INSTALL:
454 			{
455 				PackageList& packageList
456 					= dynamic_cast<InstalledRepository*>(package->Repository())
457 							!= NULL
458 						? potentialBasePackages
459 						: packagesToActivate;
460 				if (!packageList.AddItem(package))
461 					throw std::bad_alloc();
462 				break;
463 			}
464 
465 			case BSolverResultElement::B_TYPE_UNINSTALL:
466 				if (!packagesToDeactivate.AddItem(package))
467 					throw std::bad_alloc();
468 				break;
469 		}
470 	}
471 
472 	// Make sure base packages are installed in the same location.
473 	for (int32 i = 0; i < packagesToActivate.CountItems(); i++) {
474 		BSolverPackage* package = packagesToActivate.ItemAt(i);
475 		int32 index = _FindBasePackage(potentialBasePackages, package->Info());
476 		if (index < 0)
477 			continue;
478 
479 		BSolverPackage* basePackage = potentialBasePackages.RemoveItemAt(index);
480 		if (!packagesToActivate.AddItem(basePackage))
481 			throw std::bad_alloc();
482 	}
483 
484 	fInstallationInterface->ResultComputed(installationRepository);
485 }
486 
487 
488 void
489 BPackageManager::_ConfirmChanges(bool fromMostSpecific)
490 {
491 	// check, if there are any changes at all
492 	int32 count = fInstalledRepositories.CountItems();
493 	bool hasChanges = false;
494 	for (int32 i = 0; i < count; i++) {
495 		if (fInstalledRepositories.ItemAt(i)->HasChanges()) {
496 			hasChanges = true;
497 			break;
498 		}
499 	}
500 
501 	if (!hasChanges)
502 		throw BNothingToDoException();
503 
504 	fUserInteractionHandler->ConfirmChanges(fromMostSpecific);
505 }
506 
507 
508 void
509 BPackageManager::_ApplyPackageChanges(bool fromMostSpecific)
510 {
511 	int32 count = fInstalledRepositories.CountItems();
512 	if (fromMostSpecific) {
513 		for (int32 i = count - 1; i >= 0; i--)
514 			_PreparePackageChanges(*fInstalledRepositories.ItemAt(i));
515 	} else {
516 		for (int32 i = 0; i < count; i++)
517 			_PreparePackageChanges(*fInstalledRepositories.ItemAt(i));
518 	}
519 
520 	for (int32 i = 0; Transaction* transaction = fTransactions.ItemAt(i); i++)
521 		_CommitPackageChanges(*transaction);
522 
523 // TODO: Clean up the transaction directories on error!
524 }
525 
526 
527 void
528 BPackageManager::_PreparePackageChanges(
529 	InstalledRepository& installationRepository)
530 {
531 	if (!installationRepository.HasChanges())
532 		return;
533 
534 	PackageList& packagesToActivate
535 		= installationRepository.PackagesToActivate();
536 	PackageList& packagesToDeactivate
537 		= installationRepository.PackagesToDeactivate();
538 
539 	// create the transaction
540 	Transaction* transaction = new Transaction(installationRepository);
541 	if (!fTransactions.AddItem(transaction)) {
542 		delete transaction;
543 		throw std::bad_alloc();
544 	}
545 
546 	status_t error = fInstallationInterface->PrepareTransaction(*transaction);
547 	if (error != B_OK)
548 		DIE(error, "Failed to create transaction");
549 
550 	// download the new packages and prepare the transaction
551 	for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i);
552 		i++) {
553 		// get package URL and target entry
554 
555 		BString fileName(package->Info().FileName());
556 		if (fileName.IsEmpty())
557 			throw std::bad_alloc();
558 
559 		BEntry entry;
560 		error = entry.SetTo(&transaction->TransactionDirectory(), fileName);
561 		if (error != B_OK)
562 			DIE(error, "Failed to create package entry");
563 
564 		RemoteRepository* remoteRepository
565 			= dynamic_cast<RemoteRepository*>(package->Repository());
566 		if (remoteRepository != NULL) {
567 			bool reusingDownload = false;
568 
569 			// Check for matching files in already existing transaction
570 			// directories
571 			BPath path(&transaction->TransactionDirectory());
572 			BPath parent;
573 			if (path.GetParent(&parent) == B_OK) {
574 				BString globPath = parent.Path();
575 				globPath << "/*/" << fileName;
576 				glob_t globbuf;
577 				if (glob(globPath.String(), GLOB_NOSORT, NULL, &globbuf) == 0) {
578 					off_t bestSize = 0;
579 					const char* bestFile = NULL;
580 
581 					// If there are multiple matching files, pick the largest
582 					// one (the others are most likely partial downloads)
583 					for (size_t i = 0; i < globbuf.gl_pathc; i++) {
584 						off_t size = 0;
585 						BNode node(globbuf.gl_pathv[i]);
586 						if (node.GetSize(&size) == B_OK && size > bestSize) {
587 							bestSize = size;
588 							bestFile = globbuf.gl_pathv[i];
589 						}
590 					}
591 
592 					// Copy the selected file into our own transaction directory
593 					path.Append(fileName);
594 					if (bestFile != NULL && BCopyEngine().CopyEntry(bestFile,
595 						path.Path()) == B_OK) {
596 						reusingDownload = true;
597 						printf("Re-using download '%s' from previous "
598 							"transaction%s\n", bestFile,
599 							FetchUtils::IsDownloadCompleted(
600 								path.Path()) ? "" : " (partial)");
601 					}
602 					globfree(&globbuf);
603 				}
604 			}
605 
606 			// download the package (this will resume the download if the
607 			// file already exists)
608 			BString url = remoteRepository->Config().PackagesURL();
609 			url << '/' << fileName;
610 
611 			status_t error;
612 retryDownload:
613 			error = DownloadPackage(url, entry,
614 				package->Info().Checksum());
615 			if (error != B_OK) {
616 				if (error == B_BAD_DATA || error == ERANGE) {
617 					// B_BAD_DATA is returned when there is a checksum
618 					// mismatch. Make sure this download is not re-used.
619 					entry.Remove();
620 
621 					if (reusingDownload) {
622 						// Maybe the download we reused had some problem.
623 						// Try again, this time without reusing the download.
624 						printf("\nPrevious download '%s' was invalid. Redownloading.\n",
625 							path.Path());
626 						reusingDownload = false;
627 						goto retryDownload;
628 					}
629 				}
630 				DIE(error, "Failed to download package %s",
631 					package->Info().Name().String());
632 			}
633 		} else if (package->Repository() != &installationRepository) {
634 			// clone the existing package
635 			LocalRepository* localRepository
636 				= dynamic_cast<LocalRepository*>(package->Repository());
637 			if (localRepository == NULL) {
638 				DIE("Internal error: repository %s is not a local repository",
639 					package->Repository()->Name().String());
640 			}
641 			_ClonePackageFile(localRepository, package, entry);
642 		}
643 
644 		// add package to transaction
645 		if (!transaction->ActivationTransaction().AddPackageToActivate(
646 				fileName)) {
647 			throw std::bad_alloc();
648 		}
649 	}
650 
651 	for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i);
652 		i++) {
653 		// add package to transaction
654 		if (!transaction->ActivationTransaction().AddPackageToDeactivate(
655 				package->Info().FileName())) {
656 			throw std::bad_alloc();
657 		}
658 	}
659 }
660 
661 
662 void
663 BPackageManager::_CommitPackageChanges(Transaction& transaction)
664 {
665 	InstalledRepository& installationRepository = transaction.Repository();
666 
667 	fUserInteractionHandler->ProgressStartApplyingChanges(
668 		installationRepository);
669 
670 	// commit the transaction
671 	BCommitTransactionResult transactionResult;
672 	status_t error = fInstallationInterface->CommitTransaction(transaction,
673 		transactionResult);
674 	if (error != B_OK)
675 		DIE(error, "Failed to commit transaction");
676 	if (transactionResult.Error() != B_TRANSACTION_OK)
677 		DIE(transactionResult);
678 
679 	fUserInteractionHandler->ProgressTransactionCommitted(
680 		installationRepository, transactionResult);
681 
682 	BEntry transactionDirectoryEntry;
683 	if ((error = transaction.TransactionDirectory()
684 			.GetEntry(&transactionDirectoryEntry)) != B_OK
685 		|| (error = transactionDirectoryEntry.Remove()) != B_OK) {
686 		fUserInteractionHandler->Warn(error,
687 			B_TRANSLATE("Failed to remove transaction directory"));
688 	}
689 
690 	fUserInteractionHandler->ProgressApplyingChangesDone(
691 		installationRepository);
692 }
693 
694 
695 void
696 BPackageManager::_ClonePackageFile(LocalRepository* repository,
697 	BSolverPackage* package, const BEntry& entry)
698 {
699 	// get source and destination path
700 	BPath sourcePath;
701 	repository->GetPackagePath(package, sourcePath);
702 
703 	BPath destinationPath;
704 	status_t error = entry.GetPath(&destinationPath);
705 	if (error != B_OK) {
706 		DIE(error, "Failed to entry path of package file to install \"%s\"",
707 			package->Info().FileName().String());
708 	}
709 
710 	// Copy the package. Ideally we would just hard-link it, but BFS doesn't
711 	// support that.
712 	error = BCopyEngine().CopyEntry(sourcePath.Path(), destinationPath.Path());
713 	if (error != B_OK)
714 		DIE(error, "Failed to copy package file \"%s\"", sourcePath.Path());
715 }
716 
717 
718 int32
719 BPackageManager::_FindBasePackage(const PackageList& packages,
720 	const BPackageInfo& info)
721 {
722 	if (info.BasePackage().IsEmpty())
723 		return -1;
724 
725 	// find the requirement matching the base package
726 	BPackageResolvableExpression* basePackage = NULL;
727 	int32 count = info.RequiresList().CountItems();
728 	for (int32 i = 0; i < count; i++) {
729 		BPackageResolvableExpression* requires = info.RequiresList().ItemAt(i);
730 		if (requires->Name() == info.BasePackage()) {
731 			basePackage = requires;
732 			break;
733 		}
734 	}
735 
736 	if (basePackage == NULL) {
737 		fUserInteractionHandler->Warn(B_OK, B_TRANSLATE("Package %s-%s "
738 			"doesn't have a matching requires for its base package \"%s\""),
739 			info.Name().String(), info.Version().ToString().String(),
740 			info.BasePackage().String());
741 		return -1;
742 	}
743 
744 	// find the first package matching the base package requires
745 	count = packages.CountItems();
746 	for (int32 i = 0; i < count; i++) {
747 		BSolverPackage* package = packages.ItemAt(i);
748 		if (package->Name() == basePackage->Name()
749 			&& package->Info().Matches(*basePackage)) {
750 			return i;
751 		}
752 	}
753 
754 	return -1;
755 }
756 
757 
758 void
759 BPackageManager::_AddInstalledRepository(InstalledRepository* repository)
760 {
761 	fInstallationInterface->InitInstalledRepository(*repository);
762 
763 	BRepositoryBuilder(*repository)
764 		.AddToSolver(fSolver, repository->Location() == fLocation);
765 	repository->SetPriority(repository->InitialPriority());
766 
767 	if (!fInstalledRepositories.AddItem(repository))
768 		throw std::bad_alloc();
769 }
770 
771 
772 void
773 BPackageManager::_AddRemoteRepository(BPackageRoster& roster, const char* name,
774 	bool refresh)
775 {
776 	BRepositoryConfig config;
777 	status_t error = roster.GetRepositoryConfig(name, &config);
778 	if (error != B_OK) {
779 		fUserInteractionHandler->Warn(error, B_TRANSLATE(
780 			"Failed to get config for repository \"%s\". Skipping."), name);
781 		return;
782 	}
783 
784 	BRepositoryCache cache;
785 	error = _GetRepositoryCache(roster, config, refresh, cache);
786 	if (error != B_OK) {
787 		fUserInteractionHandler->Warn(error, B_TRANSLATE(
788 			"Failed to get cache for repository \"%s\". Skipping."), name);
789 		return;
790 	}
791 
792 	RemoteRepository* repository = new RemoteRepository(config);
793 	if (!fOtherRepositories.AddItem(repository)) {
794 		delete repository;
795 		throw std::bad_alloc();
796 	}
797 
798 	BRepositoryBuilder(*repository, cache, config.Name())
799 		.AddToSolver(fSolver, false);
800 }
801 
802 
803 status_t
804 BPackageManager::_GetRepositoryCache(BPackageRoster& roster,
805 	const BRepositoryConfig& config, bool refresh, BRepositoryCache& _cache)
806 {
807 	if (!refresh && roster.GetRepositoryCache(config.Name(), &_cache) == B_OK)
808 		return B_OK;
809 
810 	status_t error = RefreshRepository(config);
811 	if (error != B_OK) {
812 		fUserInteractionHandler->Warn(error, B_TRANSLATE(
813 			"Refreshing repository \"%s\" failed"), config.Name().String());
814 	}
815 
816 	return roster.GetRepositoryCache(config.Name(), &_cache);
817 }
818 
819 
820 void
821 BPackageManager::_AddPackageSpecifiers(const char* const* searchStrings,
822 	int searchStringCount, BSolverPackageSpecifierList& specifierList)
823 {
824 	for (int i = 0; i < searchStringCount; i++) {
825 		const char* searchString = searchStrings[i];
826 		if (_IsLocalPackage(searchString)) {
827 			BSolverPackage* package = _AddLocalPackage(searchString);
828 			if (!specifierList.AppendSpecifier(package))
829 				throw std::bad_alloc();
830 		} else {
831 			if (!specifierList.AppendSpecifier(searchString))
832 				throw std::bad_alloc();
833 		}
834 	}
835 }
836 
837 
838 bool
839 BPackageManager::_IsLocalPackage(const char* fileName)
840 {
841 	// Simple heuristic: fileName contains ".hpkg" and there's actually a file
842 	// it refers to.
843 	struct stat st;
844 	return strstr(fileName, ".hpkg") != NULL && stat(fileName, &st) == 0
845 		&& S_ISREG(st.st_mode);
846 }
847 
848 
849 BSolverPackage*
850 BPackageManager::_AddLocalPackage(const char* fileName)
851 {
852 	if (fLocalRepository == NULL)
853 		throw std::bad_alloc();
854 	return fLocalRepository->AddLocalPackage(fileName);
855 }
856 
857 
858 bool
859 BPackageManager::_NextSpecificInstallationLocation()
860 {
861 	try {
862 		if (fLocation == B_PACKAGE_INSTALLATION_LOCATION_SYSTEM) {
863 			fLocation = B_PACKAGE_INSTALLATION_LOCATION_HOME;
864 			fSystemRepository->SetInstalled(false);
865 			_AddInstalledRepository(fHomeRepository);
866 			return true;
867 		}
868 	} catch (BFatalErrorException& e) {
869 		// No home repo. This is acceptable for example when we are in an haikuporter chroot.
870 	}
871 
872 	return false;
873 }
874 
875 
876 status_t
877 BPackageManager::DownloadPackage(const BString& fileURL,
878 	const BEntry& targetEntry, const BString& checksum)
879 {
880 	BDecisionProvider provider;
881 	BContext context(provider, *this);
882 	return DownloadFileRequest(context, fileURL, targetEntry, checksum)
883 		.Process();
884 }
885 
886 
887 status_t
888 BPackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
889 {
890 	BDecisionProvider provider;
891 	BContext context(provider, *this);
892 	return BRefreshRepositoryRequest(context, repoConfig).Process();
893 }
894 
895 
896 // #pragma mark - RemoteRepository
897 
898 
899 BPackageManager::RemoteRepository::RemoteRepository(
900 	const BRepositoryConfig& config)
901 	:
902 	BSolverRepository(),
903 	fConfig(config)
904 {
905 }
906 
907 
908 const BRepositoryConfig&
909 BPackageManager::RemoteRepository::Config() const
910 {
911 	return fConfig;
912 }
913 
914 
915 // #pragma mark - LocalRepository
916 
917 
918 BPackageManager::LocalRepository::LocalRepository()
919 	:
920 	BSolverRepository()
921 {
922 }
923 
924 
925 BPackageManager::LocalRepository::LocalRepository(const BString& name)
926 	:
927 	BSolverRepository(name)
928 {
929 }
930 
931 
932 // #pragma mark - MiscLocalRepository
933 
934 
935 BPackageManager::MiscLocalRepository::MiscLocalRepository()
936 	:
937 	LocalRepository("local"),
938 	fPackagePaths()
939 {
940 	SetPriority(-127);
941 }
942 
943 
944 BSolverPackage*
945 BPackageManager::MiscLocalRepository::AddLocalPackage(const char* fileName)
946 {
947 	BSolverPackage* package;
948 	BRepositoryBuilder(*this).AddPackage(fileName, &package);
949 
950 	fPackagePaths[package] = fileName;
951 
952 	return package;
953 }
954 
955 
956 void
957 BPackageManager::MiscLocalRepository::GetPackagePath(BSolverPackage* package,
958 	BPath& _path)
959 {
960 	PackagePathMap::const_iterator it = fPackagePaths.find(package);
961 	if (it == fPackagePaths.end()) {
962 		DIE("Package %s not in local repository",
963 			package->VersionedName().String());
964 	}
965 
966 	status_t error = _path.SetTo(it->second.c_str());
967 	if (error != B_OK)
968 		DIE(error, "Failed to init package path %s", it->second.c_str());
969 }
970 
971 
972 // #pragma mark - InstalledRepository
973 
974 
975 BPackageManager::InstalledRepository::InstalledRepository(const char* name,
976 	BPackageInstallationLocation location, int32 priority)
977 	:
978 	LocalRepository(),
979 	fDisabledPackages(10, true),
980 	fPackagesToActivate(),
981 	fPackagesToDeactivate(),
982 	fInitialName(name),
983 	fLocation(location),
984 	fInitialPriority(priority)
985 {
986 }
987 
988 
989 void
990 BPackageManager::InstalledRepository::GetPackagePath(BSolverPackage* package,
991 	BPath& _path)
992 {
993 	directory_which packagesWhich;
994 	switch (fLocation) {
995 		case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM:
996 			packagesWhich = B_SYSTEM_PACKAGES_DIRECTORY;
997 			break;
998 		case B_PACKAGE_INSTALLATION_LOCATION_HOME:
999 			packagesWhich = B_USER_PACKAGES_DIRECTORY;
1000 			break;
1001 		default:
1002 			DIE("Don't know packages directory path for installation location "
1003 				"\"%s\"", Name().String());
1004 	}
1005 
1006 	BString fileName(package->Info().FileName());
1007 	status_t error = find_directory(packagesWhich, &_path);
1008 	if (error != B_OK || (error = _path.Append(fileName)) != B_OK) {
1009 		DIE(error, "Failed to get path of package file \"%s\" in installation "
1010 			"location \"%s\"", fileName.String(), Name().String());
1011 	}
1012 }
1013 
1014 
1015 void
1016 BPackageManager::InstalledRepository::DisablePackage(BSolverPackage* package)
1017 {
1018 	if (fDisabledPackages.HasItem(package))
1019 		DIE("Package %s already disabled", package->VersionedName().String());
1020 
1021 	if (package->Repository() != this) {
1022 		DIE("Package %s not in repository %s",
1023 			package->VersionedName().String(), Name().String());
1024 	}
1025 
1026 	// move to disabled list
1027 	if (!fDisabledPackages.AddItem(package))
1028 		throw std::bad_alloc();
1029 
1030 	RemovePackage(package);
1031 }
1032 
1033 
1034 bool
1035 BPackageManager::InstalledRepository::EnablePackage(BSolverPackage* package)
1036 {
1037 	return fDisabledPackages.RemoveItem(package);
1038 }
1039 
1040 
1041 bool
1042 BPackageManager::InstalledRepository::HasChanges() const
1043 {
1044 	return !fPackagesToActivate.IsEmpty() || !fPackagesToDeactivate.IsEmpty();
1045 }
1046 
1047 
1048 void
1049 BPackageManager::InstalledRepository::ApplyChanges()
1050 {
1051 	// disable packages to deactivate
1052 	for (int32 i = 0; BSolverPackage* package = fPackagesToDeactivate.ItemAt(i);
1053 		i++) {
1054 		if (!fDisabledPackages.HasItem(package))
1055 			DisablePackage(package);
1056 	}
1057 
1058 	// add packages to activate
1059 	for (int32 i = 0; BSolverPackage* package = fPackagesToActivate.ItemAt(i);
1060 		i++) {
1061 		status_t error = AddPackage(package->Info());
1062 		if (error != B_OK) {
1063 			DIE(error, "Failed to add package %s to %s repository",
1064 				package->Name().String(), Name().String());
1065 		}
1066 	}
1067 }
1068 
1069 
1070 // #pragma mark - Transaction
1071 
1072 
1073 BPackageManager::Transaction::Transaction(InstalledRepository& repository)
1074 	:
1075 	fRepository(repository),
1076 	fTransaction(),
1077 	fTransactionDirectory()
1078 {
1079 }
1080 
1081 
1082 BPackageManager::Transaction::~Transaction()
1083 {
1084 }
1085 
1086 
1087 // #pragma mark - InstallationInterface
1088 
1089 
1090 BPackageManager::InstallationInterface::~InstallationInterface()
1091 {
1092 }
1093 
1094 
1095 void
1096 BPackageManager::InstallationInterface::ResultComputed(
1097 	InstalledRepository& repository)
1098 {
1099 }
1100 
1101 
1102 // #pragma mark - ClientInstallationInterface
1103 
1104 
1105 BPackageManager::ClientInstallationInterface::ClientInstallationInterface()
1106 	:
1107 	fDaemonClient()
1108 {
1109 }
1110 
1111 
1112 BPackageManager::ClientInstallationInterface::~ClientInstallationInterface()
1113 {
1114 }
1115 
1116 
1117 void
1118 BPackageManager::ClientInstallationInterface::InitInstalledRepository(
1119 	InstalledRepository& repository)
1120 {
1121 	const char* name = repository.InitialName();
1122 	BRepositoryBuilder(repository, name)
1123 		.AddPackages(repository.Location(), name);
1124 }
1125 
1126 
1127 status_t
1128 BPackageManager::ClientInstallationInterface::PrepareTransaction(
1129 	Transaction& transaction)
1130 {
1131 	return fDaemonClient.CreateTransaction(transaction.Repository().Location(),
1132 		transaction.ActivationTransaction(),
1133 		transaction.TransactionDirectory());
1134 }
1135 
1136 
1137 status_t
1138 BPackageManager::ClientInstallationInterface::CommitTransaction(
1139 	Transaction& transaction, BCommitTransactionResult& _result)
1140 {
1141 	return fDaemonClient.CommitTransaction(transaction.ActivationTransaction(),
1142 		_result);
1143 }
1144 
1145 
1146 // #pragma mark - UserInteractionHandler
1147 
1148 
1149 BPackageManager::UserInteractionHandler::~UserInteractionHandler()
1150 {
1151 }
1152 
1153 
1154 void
1155 BPackageManager::UserInteractionHandler::HandleProblems()
1156 {
1157 	throw BAbortedByUserException();
1158 }
1159 
1160 
1161 void
1162 BPackageManager::UserInteractionHandler::ConfirmChanges(bool fromMostSpecific)
1163 {
1164 	throw BAbortedByUserException();
1165 }
1166 
1167 
1168 void
1169 BPackageManager::UserInteractionHandler::Warn(status_t error,
1170 	const char* format, ...)
1171 {
1172 }
1173 
1174 
1175 void
1176 BPackageManager::UserInteractionHandler::ProgressPackageDownloadStarted(
1177 	const char* packageName)
1178 {
1179 }
1180 
1181 
1182 void
1183 BPackageManager::UserInteractionHandler::ProgressPackageDownloadActive(
1184 	const char* packageName, float completionPercentage, off_t bytes,
1185 	off_t totalBytes)
1186 {
1187 }
1188 
1189 
1190 void
1191 BPackageManager::UserInteractionHandler::ProgressPackageDownloadComplete(
1192 	const char* packageName)
1193 {
1194 }
1195 
1196 
1197 void
1198 BPackageManager::UserInteractionHandler::ProgressPackageChecksumStarted(
1199 	const char* title)
1200 {
1201 }
1202 
1203 
1204 void
1205 BPackageManager::UserInteractionHandler::ProgressPackageChecksumComplete(
1206 	const char* title)
1207 {
1208 }
1209 
1210 
1211 void
1212 BPackageManager::UserInteractionHandler::ProgressStartApplyingChanges(
1213 	InstalledRepository& repository)
1214 {
1215 }
1216 
1217 
1218 void
1219 BPackageManager::UserInteractionHandler::ProgressTransactionCommitted(
1220 	InstalledRepository& repository, const BCommitTransactionResult& result)
1221 {
1222 }
1223 
1224 
1225 void
1226 BPackageManager::UserInteractionHandler::ProgressApplyingChangesDone(
1227 	InstalledRepository& repository)
1228 {
1229 }
1230 
1231 
1232 }	// namespace BPrivate
1233 
1234 }	// namespace BManager
1235 
1236 }	// namespace BPackageKit
1237