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