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