xref: /haiku/src/bin/pkgman/PackageManager.cpp (revision 204dee708a999d5a71d0cb9497650ee7cef85d0a)
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 "PackageManager.h"
11 
12 #include <Directory.h>
13 #include <package/DownloadFileRequest.h>
14 #include <package/PackageRoster.h>
15 #include <package/RefreshRepositoryRequest.h>
16 #include <package/solver/SolverPackage.h>
17 #include <package/solver/SolverPackageSpecifier.h>
18 #include <package/solver/SolverPackageSpecifierList.h>
19 #include <package/solver/SolverProblem.h>
20 #include <package/solver/SolverProblemSolution.h>
21 #include <package/solver/SolverResult.h>
22 
23 #include <package/ActivationTransaction.h>
24 #include <package/DaemonClient.h>
25 
26 #include "pkgman.h"
27 #include "RepositoryBuilder.h"
28 
29 
30 using namespace BPackageKit::BPrivate;
31 
32 
33 // #pragma mark - Repository
34 
35 
36 PackageManager::Repository::Repository()
37 	:
38 	BSolverRepository()
39 {
40 }
41 
42 
43 status_t
44 PackageManager::Repository::Init(BPackageRoster& roster, BContext& context,
45 	const char* name)
46 {
47 	// get the repository config
48 	status_t error = roster.GetRepositoryConfig(name, &fConfig);
49 	if (error != B_OK)
50 		return error;
51 
52 	// refresh
53 	BRefreshRepositoryRequest refreshRequest(context, fConfig);
54 	error = refreshRequest.Process();
55 	if (error != B_OK) {
56 		WARN(error, "refreshing repository \"%s\" failed", name);
57 		return B_OK;
58 	}
59 
60 	// re-get the config
61 	return roster.GetRepositoryConfig(name, &fConfig);
62 }
63 
64 
65 const BRepositoryConfig&
66 PackageManager::Repository::Config() const
67 {
68 	return fConfig;
69 }
70 
71 
72 // #pragma mark - Solver
73 
74 
75 PackageManager::PackageManager(BPackageInstallationLocation location,
76 	bool addInstalledRepositories, bool addOtherRepositories)
77 	:
78 	fLocation(location),
79 	fSolver(NULL),
80 	fSystemRepository(),
81 	fCommonRepository(),
82 	fHomeRepository(),
83 	fInstalledRepositories(10),
84 	fOtherRepositories(10, true),
85 	fDecisionProvider(),
86 	fJobStateListener(),
87 	fContext(fDecisionProvider, fJobStateListener)
88 {
89 	// create the solver
90 	status_t error = BSolver::Create(fSolver);
91 	if (error != B_OK)
92 		DIE(error, "failed to create solver");
93 
94 	// add installation location repositories
95 	if (addInstalledRepositories) {
96 		// We add only the repository of our actual installation location as the
97 		// "installed" repository. The repositories for the more general
98 		// installation locations are added as regular repositories, but with
99 		// better priorities than the actual (remote) repositories. This
100 		// prevents the solver from showing conflicts when a package in a more
101 		// specific installation location overrides a package in a more general
102 		// one. Instead any requirement that is already installed in a more
103 		// general installation location will turn up as to be installed as
104 		// well. But we can easily filter those out.
105 		RepositoryBuilder(fSystemRepository, "system")
106 			.AddPackages(B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, "system")
107 			.AddToSolver(fSolver, false);
108 		fSystemRepository.SetPriority(-1);
109 
110 		bool installInHome = location == B_PACKAGE_INSTALLATION_LOCATION_HOME;
111 		RepositoryBuilder(fCommonRepository, "common")
112 			.AddPackages(B_PACKAGE_INSTALLATION_LOCATION_COMMON, "common")
113 			.AddToSolver(fSolver, !installInHome);
114 
115 		if (!fInstalledRepositories.AddItem(&fSystemRepository)
116 			|| !fInstalledRepositories.AddItem(&fCommonRepository)) {
117 			DIE(B_NO_MEMORY, "failed to add installed repositories to list");
118 		}
119 
120 		if (installInHome) {
121 			fCommonRepository.SetPriority(-2);
122 			RepositoryBuilder(fHomeRepository, "home")
123 				.AddPackages(B_PACKAGE_INSTALLATION_LOCATION_HOME, "home")
124 				.AddToSolver(fSolver, true);
125 
126 			if (!fInstalledRepositories.AddItem(&fHomeRepository))
127 				DIE(B_NO_MEMORY, "failed to add home repository to list");
128 		}
129 	}
130 
131 	// add other repositories
132 	if (addOtherRepositories) {
133 		BPackageRoster roster;
134 		BStringList repositoryNames;
135 		error = roster.GetRepositoryNames(repositoryNames);
136 		if (error != B_OK)
137 			WARN(error, "failed to get repository names");
138 
139 		int32 repositoryNameCount = repositoryNames.CountStrings();
140 		for (int32 i = 0; i < repositoryNameCount; i++) {
141 			Repository* repository = new(std::nothrow) Repository;
142 			if (repository == NULL || !fOtherRepositories.AddItem(repository))
143 				DIE(B_NO_MEMORY, "failed to create/add repository object");
144 
145 			const BString& name = repositoryNames.StringAt(i);
146 			error = repository->Init(roster, fContext, name);
147 			if (error != B_OK) {
148 				WARN(error,
149 					"failed to get config for repository \"%s\". Skipping.",
150 					name.String());
151 				fOtherRepositories.RemoveItem(repository, true);
152 				continue;
153 			}
154 
155 			RepositoryBuilder(*repository, repository->Config())
156 				.AddToSolver(fSolver, false);
157 		}
158 	}
159 }
160 
161 
162 PackageManager::~PackageManager()
163 {
164 }
165 
166 
167 void
168 PackageManager::Install(const char* const* packages, int packageCount)
169 {
170 	// solve
171 	BSolverPackageSpecifierList packagesToInstall;
172 	for (int i = 0; i < packageCount; i++) {
173 		if (!packagesToInstall.AppendSpecifier(packages[i]))
174 			DIE(B_NO_MEMORY, "failed to add specified package");
175 	}
176 
177 	const BSolverPackageSpecifier* unmatchedSpecifier;
178 	status_t error = fSolver->Install(packagesToInstall, &unmatchedSpecifier);
179 	if (error != B_OK) {
180 		if (unmatchedSpecifier != NULL) {
181 			DIE(error, "failed to find a match for \"%s\"",
182 				unmatchedSpecifier->SelectString().String());
183 		} else
184 			DIE(error, "failed to compute packages to install");
185 	}
186 
187 	_HandleProblems();
188 
189 	// install/uninstall packages
190 	_AnalyzeResult();
191 	_PrintResult();
192 	_ApplyPackageChanges();
193 }
194 
195 
196 void
197 PackageManager::Uninstall(const char* const* packages, int packageCount)
198 {
199 	// solve
200 	BSolverPackageSpecifierList packagesToUninstall;
201 	for (int i = 0; i < packageCount; i++) {
202 		if (!packagesToUninstall.AppendSpecifier(packages[i]))
203 			DIE(B_NO_MEMORY, "failed to add specified package");
204 	}
205 
206 	const BSolverPackageSpecifier* unmatchedSpecifier;
207 	status_t error = fSolver->Uninstall(packagesToUninstall,
208 		&unmatchedSpecifier);
209 	if (error != B_OK) {
210 		if (unmatchedSpecifier != NULL) {
211 			DIE(error, "failed to find a match for \"%s\"",
212 				unmatchedSpecifier->SelectString().String());
213 		} else
214 			DIE(error, "failed to compute packages to uninstall");
215 	}
216 
217 	_HandleProblems();
218 
219 	// install/uninstall packages
220 	_AnalyzeResult();
221 	_PrintResult();
222 	_ApplyPackageChanges();
223 }
224 
225 
226 void
227 PackageManager::Update(const char* const* packages, int packageCount)
228 {
229 	// solve
230 	BSolverPackageSpecifierList packagesToUpdate;
231 	for (int i = 0; i < packageCount; i++) {
232 		if (!packagesToUpdate.AppendSpecifier(packages[i]))
233 			DIE(B_NO_MEMORY, "failed to add specified package");
234 	}
235 
236 	const BSolverPackageSpecifier* unmatchedSpecifier;
237 	status_t error = fSolver->Update(packagesToUpdate, true,
238 		&unmatchedSpecifier);
239 	if (error != B_OK) {
240 		if (unmatchedSpecifier != NULL) {
241 			DIE(error, "failed to find a match for \"%s\"",
242 				unmatchedSpecifier->SelectString().String());
243 		} else
244 			DIE(error, "failed to compute packages to update");
245 	}
246 
247 	_HandleProblems();
248 
249 	// install/uninstall packages
250 	_AnalyzeResult();
251 	_PrintResult();
252 	_ApplyPackageChanges();
253 }
254 
255 
256 void
257 PackageManager::_HandleProblems()
258 {
259 	while (fSolver->HasProblems()) {
260 		printf("Encountered problems:\n");
261 
262 		int32 problemCount = fSolver->CountProblems();
263 		for (int32 i = 0; i < problemCount; i++) {
264 			// print problem and possible solutions
265 			BSolverProblem* problem = fSolver->ProblemAt(i);
266 			printf("problem %" B_PRId32 ": %s\n", i + 1,
267 				problem->ToString().String());
268 
269 			int32 solutionCount = problem->CountSolutions();
270 			for (int32 k = 0; k < solutionCount; k++) {
271 				const BSolverProblemSolution* solution = problem->SolutionAt(k);
272 				printf("  solution %" B_PRId32 ":\n", k + 1);
273 				int32 elementCount = solution->CountElements();
274 				for (int32 l = 0; l < elementCount; l++) {
275 					const BSolverProblemSolutionElement* element
276 						= solution->ElementAt(l);
277 					printf("    - %s\n", element->ToString().String());
278 				}
279 			}
280 
281 			// let the user choose a solution
282 			printf("Please select a solution, skip the problem for now or "
283 				"quit.\n");
284 			for (;;) {
285 				if (solutionCount > 1)
286 					printf("select [1...%" B_PRId32 "/s/q]: ", solutionCount);
287 				else
288 					printf("select [1/s/q]: ");
289 
290 				char buffer[32];
291 				if (fgets(buffer, sizeof(buffer), stdin) == NULL
292 					|| strcmp(buffer, "q\n") == 0) {
293 					exit(1);
294 				}
295 
296 				if (strcmp(buffer, "s\n") == 0)
297 					break;
298 
299 				char* end;
300 				long selected = strtol(buffer, &end, 0);
301 				if (end == buffer || *end != '\n' || selected < 1
302 					|| selected > solutionCount) {
303 					printf("*** invalid input\n");
304 					continue;
305 				}
306 
307 				status_t error = fSolver->SelectProblemSolution(problem,
308 					problem->SolutionAt(selected - 1));
309 				if (error != B_OK)
310 					DIE(error, "failed to set solution");
311 				break;
312 			}
313 		}
314 
315 		status_t error = fSolver->SolveAgain();
316 		if (error != B_OK)
317 			DIE(error, "failed to recompute packages to un/-install");
318 	}
319 }
320 
321 
322 void
323 PackageManager::_AnalyzeResult()
324 {
325 	BSolverResult result;
326 	status_t error = fSolver->GetResult(result);
327 	if (error != B_OK)
328 		DIE(error, "failed to compute packages to un/-install");
329 
330 	for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
331 			i++) {
332 		BSolverPackage* package = element->Package();
333 
334 		switch (element->Type()) {
335 			case BSolverResultElement::B_TYPE_INSTALL:
336 				if (!fInstalledRepositories.HasItem(package->Repository())) {
337 					if (!fPackagesToActivate.AddItem(package))
338 						DIE(B_NO_MEMORY, "failed to add package to activate");
339 				}
340 				break;
341 
342 			case BSolverResultElement::B_TYPE_UNINSTALL:
343 				if (!fPackagesToDeactivate.AddItem(package))
344 					DIE(B_NO_MEMORY, "failed to add package to deactivate");
345 				break;
346 		}
347 	}
348 
349 	if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.IsEmpty()) {
350 		printf("Nothing to do.\n");
351 		exit(0);
352 	}
353 }
354 
355 
356 void
357 PackageManager::_PrintResult()
358 {
359 	printf("The following changes will be made:\n");
360 	for (int32 i = 0; BSolverPackage* package = fPackagesToActivate.ItemAt(i);
361 		i++) {
362 		printf("  install package %s from repository %s\n",
363 			package->Info().CanonicalFileName().String(),
364 			package->Repository()->Name().String());
365 	}
366 
367 	for (int32 i = 0; BSolverPackage* package = fPackagesToDeactivate.ItemAt(i);
368 		i++) {
369 		printf("  uninstall package %s\n", package->VersionedName().String());
370 	}
371 // TODO: Print file/download sizes. Unfortunately our package infos don't
372 // contain the file size. Which is probably correct. The file size (and possibly
373 // other information) should, however, be provided by the repository cache in
374 // some way. Extend BPackageInfo? Create a BPackageFileInfo?
375 
376 	if (!fDecisionProvider.YesNoDecisionNeeded(BString(), "Continue?", "y", "n",
377 			"y")) {
378 		exit(1);
379 	}
380 }
381 
382 
383 void
384 PackageManager::_ApplyPackageChanges()
385 {
386 	// create an activation transaction
387 	BDaemonClient daemonClient;
388 	BActivationTransaction transaction;
389 	BDirectory transactionDirectory;
390 	status_t error = daemonClient.CreateTransaction(fLocation, transaction,
391 		transactionDirectory);
392 	if (error != B_OK)
393 		DIE(error, "failed to create transaction");
394 
395 	// download the new packages and prepare the transaction
396 	for (int32 i = 0; BSolverPackage* package = fPackagesToActivate.ItemAt(i);
397 		i++) {
398 		// get package URL and target entry
399 		Repository* repository
400 			= static_cast<Repository*>(package->Repository());
401 		BString url = repository->Config().BaseURL();
402 		BString fileName(package->Info().CanonicalFileName());
403 		if (fileName.IsEmpty())
404 			DIE(B_NO_MEMORY, "failed to allocate file name");
405 		url << '/' << fileName;
406 
407 		BEntry entry;
408 		error = entry.SetTo(&transactionDirectory, fileName);
409 		if (error != B_OK)
410 			DIE(error, "failed to create package entry");
411 
412 		// download the package
413 		DownloadFileRequest downloadRequest(fContext, url, entry,
414 			package->Info().Checksum());
415 		error = downloadRequest.Process();
416 		if (error != B_OK)
417 			DIE(error, "failed to download package");
418 
419 		// add package to transaction
420 		if (!transaction.AddPackageToActivate(
421 				package->Info().CanonicalFileName())) {
422 			DIE(B_NO_MEMORY,
423 				"failed to add package to activate to transaction");
424 		}
425 	}
426 
427 	for (int32 i = 0; BSolverPackage* package = fPackagesToDeactivate.ItemAt(i);
428 		i++) {
429 		// add package to transaction
430 		if (!transaction.AddPackageToDeactivate(
431 				package->Info().CanonicalFileName())) {
432 			DIE(B_NO_MEMORY,
433 				"failed to add package to deactivate to transaction");
434 		}
435 	}
436 
437 	// commit the transaction
438 	BDaemonClient::BCommitTransactionResult transactionResult;
439 	error = daemonClient.CommitTransaction(transaction, transactionResult);
440 	if (error != B_OK) {
441 		fprintf(stderr, "*** failed to commit transaction: %s\n",
442 			transactionResult.FullErrorMessage().String());
443 		exit(1);
444 	}
445 
446 	printf("Installation done. Old activation state backed up in \"%s\"\n",
447 		transactionResult.OldStateDirectory().String());
448 
449 	printf("Cleaning up ...\n");
450 	BEntry transactionDirectoryEntry;
451 	if ((error = transactionDirectory.GetEntry(&transactionDirectoryEntry))
452 			!= B_OK
453 		|| (error = transactionDirectoryEntry.Remove()) != B_OK) {
454 		WARN(error, "failed to remove transaction directory");
455 	}
456 }
457