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