work on requests with the correct upgrade/dist-upgrade/else resolver
[ntk/apt.git] / apt-pkg / edsp.cc
CommitLineData
6d38011b
DK
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
3/* ######################################################################
4 Set of methods to help writing and reading everything needed for EDSP
5 ##################################################################### */
6 /*}}}*/
7// Include Files /*{{{*/
c3b85126 8#include <apt-pkg/edsp.h>
6d38011b
DK
9#include <apt-pkg/error.h>
10#include <apt-pkg/configuration.h>
11#include <apt-pkg/version.h>
12#include <apt-pkg/policy.h>
2029276f 13#include <apt-pkg/tagfile.h>
6d38011b
DK
14
15#include <apti18n.h>
16#include <limits>
17
18#include <stdio.h>
19 /*}}}*/
20
d4f626ff
DK
21// we could use pkgCache::DepType and ::Priority, but these would be localized stringsā€¦
22const char * const EDSP::PrioMap[] = {0, "important", "required", "standard",
23 "optional", "extra"};
24const char * const EDSP::DepMap[] = {"", "Depends", "PreDepends", "Suggests",
25 "Recommends" , "Conflicts", "Replaces",
26 "Obsoletes", "Breaks", "Enhances"};
27
c3b85126
DK
28// EDSP::WriteScenario - to the given file descriptor /*{{{*/
29bool EDSP::WriteScenario(pkgDepCache &Cache, FILE* output)
6d38011b 30{
6d38011b 31 for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
6d38011b
DK
32 for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
33 {
d4f626ff
DK
34 WriteScenarioVersion(Cache, output, Pkg, Ver);
35 WriteScenarioDependency(Cache, output, Pkg, Ver);
6d38011b
DK
36 fprintf(output, "\n");
37 }
6d38011b
DK
38 return true;
39}
40 /*}}}*/
d4f626ff
DK
41// EDSP::WriteLimitedScenario - to the given file descriptor /*{{{*/
42bool EDSP::WriteLimitedScenario(pkgDepCache &Cache, FILE* output,
43 APT::PackageSet const &pkgset)
44{
45 for (APT::PackageSet::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg)
46 for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
47 {
48 WriteScenarioVersion(Cache, output, Pkg, Ver);
49 WriteScenarioLimitedDependency(Cache, output, Pkg, Ver, pkgset);
50 fprintf(output, "\n");
51 }
52 return true;
53}
54 /*}}}*/
55// EDSP::WriteScenarioVersion /*{{{*/
56void EDSP::WriteScenarioVersion(pkgDepCache &Cache, FILE* output, pkgCache::PkgIterator const &Pkg,
57 pkgCache::VerIterator const &Ver)
58{
59 fprintf(output, "Package: %s\n", Pkg.Name());
60 fprintf(output, "Architecture: %s\n", Ver.Arch());
61 fprintf(output, "Version: %s\n", Ver.VerStr());
62 if (Pkg.CurrentVer() == Ver)
63 fprintf(output, "Installed: yes\n");
64 if (Pkg->SelectedState == pkgCache::State::Hold)
65 fprintf(output, "Hold: yes\n");
66 fprintf(output, "APT-ID: %d\n", Ver->ID);
67 fprintf(output, "Priority: %s\n", PrioMap[Ver->Priority]);
68 if ((Pkg->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential)
69 fprintf(output, "Essential: yes\n");
70 fprintf(output, "Section: %s\n", Ver.Section());
71 if (Ver->MultiArch == pkgCache::Version::Allowed || Ver->MultiArch == pkgCache::Version::AllAllowed)
72 fprintf(output, "Multi-Arch: allowed\n");
73 else if (Ver->MultiArch == pkgCache::Version::Foreign || Ver->MultiArch == pkgCache::Version::AllForeign)
74 fprintf(output, "Multi-Arch: foreign\n");
75 else if (Ver->MultiArch == pkgCache::Version::Same)
76 fprintf(output, "Multi-Arch: same\n");
77 signed short Pin = std::numeric_limits<signed short>::min();
78 for (pkgCache::VerFileIterator File = Ver.FileList(); File.end() == false; ++File) {
79 signed short const p = Cache.GetPolicy().GetPriority(File.File());
80 if (Pin < p)
81 Pin = p;
82 }
83 fprintf(output, "APT-Pin: %d\n", Pin);
84 if (Cache.GetCandidateVer(Pkg) == Ver)
85 fprintf(output, "APT-Candidate: yes\n");
86 if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
87 fprintf(output, "APT-Automatic: yes\n");
88}
89 /*}}}*/
90// EDSP::WriteScenarioDependency /*{{{*/
91void EDSP::WriteScenarioDependency(pkgDepCache &Cache, FILE* output, pkgCache::PkgIterator const &Pkg,
92 pkgCache::VerIterator const &Ver)
93{
94 std::string dependencies[pkgCache::Dep::Enhances + 1];
95 bool orGroup = false;
96 for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep)
97 {
98 // Ignore implicit dependencies for multiarch here
99 if (strcmp(Pkg.Arch(), Dep.TargetPkg().Arch()) != 0)
100 continue;
101 if (orGroup == false)
102 dependencies[Dep->Type].append(", ");
103 dependencies[Dep->Type].append(Dep.TargetPkg().Name());
104 if (Dep->Version != 0)
105 dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")");
106 if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
107 {
108 dependencies[Dep->Type].append(" | ");
109 orGroup = true;
110 }
111 else
112 orGroup = false;
113 }
114 for (int i = 1; i < pkgCache::Dep::Enhances + 1; ++i)
115 if (dependencies[i].empty() == false)
116 fprintf(output, "%s: %s\n", DepMap[i], dependencies[i].c_str()+2);
117 string provides;
118 for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv)
119 {
120 // Ignore implicit provides for multiarch here
121 if (strcmp(Pkg.Arch(), Prv.ParentPkg().Arch()) != 0 || strcmp(Pkg.Name(),Prv.Name()) == 0)
122 continue;
123 provides.append(", ").append(Prv.Name());
124 }
125 if (provides.empty() == false)
126 fprintf(output, "Provides: %s\n", provides.c_str()+2);
127}
128 /*}}}*/
129// EDSP::WriteScenarioLimitedDependency /*{{{*/
130void EDSP::WriteScenarioLimitedDependency(pkgDepCache &Cache, FILE* output,
131 pkgCache::PkgIterator const &Pkg,
132 pkgCache::VerIterator const &Ver,
133 APT::PackageSet const &pkgset)
134{
135 std::string dependencies[pkgCache::Dep::Enhances + 1];
136 bool orGroup = false;
137 for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep)
138 {
139 // Ignore implicit dependencies for multiarch here
140 if (strcmp(Pkg.Arch(), Dep.TargetPkg().Arch()) != 0)
141 continue;
142 if (orGroup == false)
143 {
144 if (pkgset.find(Dep.TargetPkg()) == pkgset.end())
145 continue;
146 dependencies[Dep->Type].append(", ");
147 }
148 else if (pkgset.find(Dep.TargetPkg()) == pkgset.end())
149 {
150 if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
151 continue;
152 dependencies[Dep->Type].erase(dependencies[Dep->Type].end()-3, dependencies[Dep->Type].end());
153 orGroup = false;
154 continue;
155 }
156 dependencies[Dep->Type].append(Dep.TargetPkg().Name());
157 if (Dep->Version != 0)
158 dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")");
159 if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
160 {
161 dependencies[Dep->Type].append(" | ");
162 orGroup = true;
163 }
164 else
165 orGroup = false;
166 }
167 for (int i = 1; i < pkgCache::Dep::Enhances + 1; ++i)
168 if (dependencies[i].empty() == false)
169 fprintf(output, "%s: %s\n", DepMap[i], dependencies[i].c_str()+2);
170 string provides;
171 for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv)
172 {
173 // Ignore implicit provides for multiarch here
174 if (strcmp(Pkg.Arch(), Prv.ParentPkg().Arch()) != 0 || strcmp(Pkg.Name(),Prv.Name()) == 0)
175 continue;
176 if (pkgset.find(Prv.ParentPkg()) == pkgset.end())
177 continue;
178 provides.append(", ").append(Prv.Name());
179 }
180 if (provides.empty() == false)
181 fprintf(output, "Provides: %s\n", provides.c_str()+2);
182}
183 /*}}}*/
c3b85126 184// EDSP::WriteRequest - to the given file descriptor /*{{{*/
93794bc9
DK
185bool EDSP::WriteRequest(pkgDepCache &Cache, FILE* output, bool const Upgrade,
186 bool const DistUpgrade, bool const AutoRemove)
6d38011b 187{
93794bc9 188 string del, inst;
6d38011b
DK
189 for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
190 {
191 string* req;
192 if (Cache[Pkg].Delete() == true)
193 req = &del;
93794bc9 194 else if (Cache[Pkg].NewInstall() == true || Cache[Pkg].Upgrade() == true)
6d38011b 195 req = &inst;
6d38011b
DK
196 else
197 continue;
6d5bd614 198 req->append(" ").append(Pkg.FullName());
6d38011b 199 }
93794bc9 200 fprintf(output, "Request: EDSP 0.2\n");
6d38011b 201 if (del.empty() == false)
6d5bd614 202 fprintf(output, "Remove: %s\n", del.c_str()+1);
6d38011b 203 if (inst.empty() == false)
6d5bd614 204 fprintf(output, "Install: %s\n", inst.c_str()+1);
93794bc9
DK
205 if (Upgrade == true)
206 fprintf(output, "Upgrade: yes\n");
207 if (DistUpgrade == true)
208 fprintf(output, "Dist-Upgrade: yes\n");
209 if (AutoRemove == true)
210 fprintf(output, "Autoremove: yes\n");
211 if (_config->FindB("APT::Solver::Strict-Pinning", true) == false)
212 fprintf(output, "Strict-Pinning: no\n");
213 string solverpref("APT::Solver::");
214 solverpref.append(_config->Find("APT::Solver::Name", "internal")).append("::Preferences");
6d5bd614 215 if (_config->Exists(solverpref) == true)
93794bc9 216 fprintf(output, "Preferences: %s\n", _config->Find(solverpref,"").c_str());
6d5bd614 217 fprintf(output, "\n");
6d38011b 218
e3674d91
DK
219 return true;
220}
221 /*}}}*/
2029276f
DK
222// EDSP::ReadResponse - from the given file descriptor /*{{{*/
223bool EDSP::ReadResponse(int const input, pkgDepCache &Cache) {
2a33cb16
DK
224 /* We build an map id to mmap offset here
225 In theory we could use the offset as ID, but then VersionCount
226 couldn't be used to create other versionmappings anymore and it
227 would be too easy for a (buggy) solver to segfault APTā€¦ */
228 unsigned long long const VersionCount = Cache.Head().VersionCount;
229 unsigned long VerIdx[VersionCount];
76d4aab0 230 for (pkgCache::PkgIterator P = Cache.PkgBegin(); P.end() == false; ++P) {
2a33cb16
DK
231 for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V)
232 VerIdx[V->ID] = V.Index();
76d4aab0
DK
233 Cache[P].Marked = true;
234 Cache[P].Garbage = false;
235 }
2a33cb16 236
c80a49f5
DK
237 FileFd in;
238 in.OpenDescriptor(input, FileFd::ReadOnly);
288a76d2 239 pkgTagFile response(&in, 100);
c80a49f5
DK
240 pkgTagSection section;
241
2029276f
DK
242 while (response.Step(section) == true) {
243 std::string type;
244 if (section.Exists("Install") == true)
245 type = "Install";
246 else if (section.Exists("Remove") == true)
247 type = "Remove";
e876223c 248 else if (section.Exists("Progress") == true) {
288a76d2 249 std::clog << TimeRFC1123(time(NULL)) << " ";
e876223c
DK
250 ioprintf(std::clog, "[ %3d%% ] ", section.FindI("Percentage", 0));
251 std::clog << section.FindS("Progress") << " - ";
252 string const msg = section.FindS("Message");
253 if (msg.empty() == true)
254 std::clog << "Solver is still working on the solution" << std::endl;
255 else
256 std::clog << msg << std::endl;
257 continue;
76d4aab0
DK
258 } else if (section.Exists("Autoremove") == true)
259 type = "Autoremove";
260 else
2029276f 261 continue;
29099cb6 262
2a33cb16
DK
263 size_t const id = section.FindULL(type.c_str(), VersionCount);
264 if (id == VersionCount) {
69a78835
DK
265 _error->Warning("Unable to parse %s request with id value '%s'!", type.c_str(), section.FindS(type.c_str()).c_str());
266 continue;
2a33cb16
DK
267 } else if (id > Cache.Head().VersionCount) {
268 _error->Warning("ID value '%s' in %s request stanza is to high to refer to a known version!", section.FindS(type.c_str()).c_str(), type.c_str());
269 continue;
69a78835 270 }
2029276f 271
2a33cb16 272 pkgCache::VerIterator Ver(Cache.GetCache(), Cache.GetCache().VerP + VerIdx[id]);
69a78835
DK
273 Cache.SetCandidateVersion(Ver);
274 if (type == "Install")
275 Cache.MarkInstall(Ver.ParentPkg(), false, false);
276 else if (type == "Remove")
277 Cache.MarkDelete(Ver.ParentPkg(), false);
76d4aab0
DK
278 else if (type == "Autoremove") {
279 Cache[Ver.ParentPkg()].Marked = false;
280 Cache[Ver.ParentPkg()].Garbage = true;
281 }
2029276f
DK
282 }
283 return true;
284}
285 /*}}}*/
6d5bd614
DK
286// EDSP::ReadLine - first line from the given file descriptor /*{{{*/
287// ---------------------------------------------------------------------
288/* Little helper method to read a complete line into a string. Similar to
289 fgets but we need to use the low-level read() here as otherwise the
290 listparser will be confused later on as mixing of fgets and read isn't
2029276f 291 a supported action according to the manpages and results are undefined */
6d5bd614
DK
292bool EDSP::ReadLine(int const input, std::string &line) {
293 char one;
294 ssize_t data = 0;
295 line.erase();
296 line.reserve(100);
297 while ((data = read(input, &one, sizeof(one))) != -1) {
298 if (data != 1)
299 continue;
300 if (one == '\n')
301 return true;
302 if (one == '\r')
303 continue;
304 if (line.empty() == true && isblank(one) != 0)
305 continue;
306 line += one;
307 }
308 return false;
309}
310 /*}}}*/
40795fca
DK
311// EDSP::StringToBool - convert yes/no to bool /*{{{*/
312// ---------------------------------------------------------------------
313/* we are not as lazy as we are in the global StringToBool as we really
314 only accept yes/no here - but we will ignore leading spaces */
315bool EDSP::StringToBool(char const *answer, bool const defValue) {
316 for (; isspace(*answer) != 0; ++answer);
317 if (strncasecmp(answer, "yes", 3) == 0)
318 return true;
319 else if (strncasecmp(answer, "no", 2) == 0)
320 return false;
321 else
322 _error->Warning("Value '%s' is not a boolean 'yes' or 'no'!", answer);
323 return defValue;
324}
325 /*}}}*/
6d5bd614
DK
326// EDSP::ReadRequest - first stanza from the given file descriptor /*{{{*/
327bool EDSP::ReadRequest(int const input, std::list<std::string> &install,
40795fca
DK
328 std::list<std::string> &remove, bool &upgrade,
329 bool &distUpgrade, bool &autoRemove)
6d5bd614 330{
40795fca
DK
331 install.clear();
332 remove.clear();
333 upgrade = false;
334 distUpgrade = false;
335 autoRemove = false;
6d5bd614
DK
336 std::string line;
337 while (ReadLine(input, line) == true)
338 {
339 // Skip empty lines before request
340 if (line.empty() == true)
341 continue;
342 // The first Tag must be a request, so search for it
40795fca 343 if (line.compare(0, 8, "Request:") != 0)
6d5bd614
DK
344 continue;
345
346 while (ReadLine(input, line) == true)
347 {
348 // empty lines are the end of the request
349 if (line.empty() == true)
350 return true;
351
352 std::list<std::string> *request = NULL;
40795fca 353 if (line.compare(0, 8, "Install:") == 0)
6d5bd614 354 {
40795fca 355 line.erase(0, 8);
6d5bd614
DK
356 request = &install;
357 }
40795fca 358 else if (line.compare(0, 7, "Remove:") == 0)
6d5bd614 359 {
40795fca 360 line.erase(0, 7);
6d5bd614
DK
361 request = &remove;
362 }
40795fca
DK
363 else if (line.compare(0, 8, "Upgrade:") == 0)
364 upgrade = EDSP::StringToBool(line.c_str() + 9, false);
365 else if (line.compare(0, 13, "Dist-Upgrade:") == 0)
366 distUpgrade = EDSP::StringToBool(line.c_str() + 14, false);
367 else if (line.compare(0, 11, "Autoremove:") == 0)
368 autoRemove = EDSP::StringToBool(line.c_str() + 12, false);
369 else
370 _error->Warning("Unknown line in EDSP Request stanza: %s", line.c_str());
371
6d5bd614
DK
372 if (request == NULL)
373 continue;
374 size_t end = line.length();
375 do {
376 size_t begin = line.rfind(' ');
377 if (begin == std::string::npos)
378 {
40795fca 379 request->push_back(line.substr(0, end));
6d5bd614
DK
380 break;
381 }
382 else if (begin < end)
383 request->push_back(line.substr(begin + 1, end));
384 line.erase(begin);
385 end = line.find_last_not_of(' ');
386 } while (end != std::string::npos);
387 }
388 }
389 return false;
390}
391 /*}}}*/
392// EDSP::ApplyRequest - first stanza from the given file descriptor /*{{{*/
c3b85126 393bool EDSP::ApplyRequest(std::list<std::string> const &install,
29099cb6
DK
394 std::list<std::string> const &remove,
395 pkgDepCache &Cache)
6d5bd614
DK
396{
397 for (std::list<std::string>::const_iterator i = install.begin();
d4f626ff
DK
398 i != install.end(); ++i) {
399 pkgCache::PkgIterator P = Cache.FindPkg(*i);
400 if (P.end() == true)
401 _error->Warning("Package %s is not known, so can't be installed", i->c_str());
402 else
403 Cache.MarkInstall(P, false);
404 }
6d5bd614
DK
405
406 for (std::list<std::string>::const_iterator i = remove.begin();
d4f626ff
DK
407 i != remove.end(); ++i) {
408 pkgCache::PkgIterator P = Cache.FindPkg(*i);
409 if (P.end() == true)
410 _error->Warning("Package %s is not known, so can't be installed", i->c_str());
411 else
412 Cache.MarkDelete(P);
413 }
6d5bd614
DK
414 return true;
415}
416 /*}}}*/
c3b85126
DK
417// EDSP::WriteSolution - to the given file descriptor /*{{{*/
418bool EDSP::WriteSolution(pkgDepCache &Cache, FILE* output)
e3674d91 419{
6d5bd614 420 bool const Debug = _config->FindB("Debug::EDSP::WriteSolution", false);
e3674d91
DK
421 for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
422 {
423 if (Cache[Pkg].Delete() == true)
d4f626ff
DK
424 {
425 fprintf(output, "Remove: %d\n", Pkg.CurrentVer()->ID);
426 if (Debug == true)
427 fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), Pkg.CurrentVer().VerStr());
428 }
e3674d91 429 else if (Cache[Pkg].NewInstall() == true || Cache[Pkg].Upgrade() == true)
d4f626ff 430 {
e3674d91 431 fprintf(output, "Install: %d\n", Cache.GetCandidateVer(Pkg)->ID);
d4f626ff
DK
432 if (Debug == true)
433 fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), Cache.GetCandidateVer(Pkg).VerStr());
434 }
76d4aab0
DK
435 else if (Cache[Pkg].Garbage == true)
436 {
437 fprintf(output, "Autoremove: %d\n", Pkg.CurrentVer()->ID);
438 if (Debug == true)
439 fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), Pkg.CurrentVer().VerStr());
440 fprintf(stderr, "Autoremove: %s\nVersion: %s\n", Pkg.FullName().c_str(), Pkg.CurrentVer().VerStr());
441 }
e3674d91
DK
442 else
443 continue;
e3674d91
DK
444 fprintf(output, "\n");
445 }
446
6d38011b
DK
447 return true;
448}
449 /*}}}*/
e876223c
DK
450// EDSP::WriteProgess - pulse to the given file descriptor /*{{{*/
451bool EDSP::WriteProgress(unsigned short const percent, const char* const message, FILE* output) {
452 fprintf(output, "Progress: %s\n", TimeRFC1123(time(NULL)).c_str());
453 fprintf(output, "Percentage: %d\n", percent);
454 fprintf(output, "Message: %s\n\n", message);
455 fflush(output);
456 return true;
457}
458 /*}}}*/
c3b85126 459bool EDSP::WriteError(std::string const &message, FILE* output) { return false; }
ac5fbff8
DK
460
461// EDSP::ExecuteSolver - fork requested solver and setup ipc pipes {{{*/
462bool EDSP::ExecuteSolver(const char* const solver, int *solver_in, int *solver_out) {
463 std::vector<std::string> const solverDirs = _config->FindVector("Dir::Bin::Solvers");
464 std::string file;
465 for (std::vector<std::string>::const_iterator dir = solverDirs.begin();
466 dir != solverDirs.end(); ++dir) {
467 file = flCombine(*dir, solver);
468 if (RealFileExists(file.c_str()) == true)
469 break;
470 file.clear();
471 }
472
473 if (file.empty() == true)
474 return _error->Error("Can't call external solver '%s' as it is not in a configured directory!", solver);
475 int external[4] = {-1, -1, -1, -1};
476 if (pipe(external) != 0 || pipe(external + 2) != 0)
477 return _error->Errno("Resolve", "Can't create needed IPC pipes for EDSP");
478 for (int i = 0; i < 4; ++i)
479 SetCloseExec(external[i], true);
480
481 pid_t Solver = ExecFork();
482 if (Solver == 0)
483 {
484 dup2(external[0], STDIN_FILENO);
485 dup2(external[3], STDOUT_FILENO);
486 const char* calling[2] = { file.c_str(), 0 };
487 execv(calling[0], (char**) calling);
488 std::cerr << "Failed to execute solver '" << solver << "'!" << std::endl;
489 _exit(100);
490 }
491 close(external[0]);
492 close(external[3]);
493
494 if (WaitFd(external[1], true, 5) == false)
495 return _error->Errno("Resolve", "Timed out while Waiting on availability of solver stdin");
496
497 *solver_in = external[1];
498 *solver_out = external[2];
499 return true;
500}
501 /*}}}*/