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