]> git.saurik.com Git - apt.git/blob - apt-pkg/deb/dpkgpm.cc
Most interfaces (Maemo) need a high-level name :/.
[apt.git] / apt-pkg / deb / dpkgpm.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 /* ######################################################################
4
5 DPKG Package Manager - Provide an interface to dpkg
6
7 ##################################################################### */
8 /*}}}*/
9 // Includes /*{{{*/
10 #include <config.h>
11
12 #include <apt-pkg/cachefile.h>
13 #include <apt-pkg/configuration.h>
14 #include <apt-pkg/depcache.h>
15 #include <apt-pkg/dpkgpm.h>
16 #include <apt-pkg/debsystem.h>
17 #include <apt-pkg/error.h>
18 #include <apt-pkg/fileutl.h>
19 #include <apt-pkg/install-progress.h>
20 #include <apt-pkg/packagemanager.h>
21 #include <apt-pkg/strutl.h>
22 #include <apt-pkg/statechanges.h>
23 #include <apt-pkg/cacheiterators.h>
24 #include <apt-pkg/macros.h>
25 #include <apt-pkg/pkgcache.h>
26 #include <apt-pkg/version.h>
27
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <grp.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <stddef.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <sys/ioctl.h>
37 #include <sys/select.h>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <sys/wait.h>
41 #include <sys/types.h>
42 #include <dirent.h>
43 #include <termios.h>
44 #include <time.h>
45 #include <unistd.h>
46
47 #include <algorithm>
48 #include <array>
49 #include <cstring>
50 #include <iostream>
51 #include <map>
52 #include <set>
53 #include <string>
54 #include <type_traits>
55 #include <utility>
56 #include <unordered_set>
57 #include <vector>
58 #include <sstream>
59 #include <numeric>
60
61 #include <apti18n.h>
62 /*}}}*/
63
64 extern char **environ;
65
66 using namespace std;
67
68 APT_PURE static string AptHistoryRequestingUser() /*{{{*/
69 {
70 const char* EnvKeys[]{"SUDO_UID", "PKEXEC_UID", "PACKAGEKIT_CALLER_UID"};
71
72 for (const auto &Key: EnvKeys)
73 {
74 if (getenv(Key) != nullptr)
75 {
76 int uid = atoi(getenv(Key));
77 if (uid > 0) {
78 struct passwd pwd;
79 struct passwd *result;
80 char buf[255];
81 if (getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0 && result != NULL) {
82 std::string res;
83 strprintf(res, "%s (%d)", pwd.pw_name, uid);
84 return res;
85 }
86 }
87 }
88 }
89 return "";
90 }
91 /*}}}*/
92 APT_PURE static unsigned int EnvironmentSize() /*{{{*/
93 {
94 unsigned int size = 0;
95 char **envp = environ;
96
97 while (*envp != NULL)
98 size += strlen (*envp++) + 1;
99
100 return size;
101 }
102 /*}}}*/
103 class pkgDPkgPMPrivate /*{{{*/
104 {
105 public:
106 pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0),
107 term_out(NULL), history_out(NULL),
108 progress(NULL), tt_is_valid(false), master(-1),
109 slave(NULL), protect_slave_from_dying(-1),
110 direct_stdin(false)
111 {
112 dpkgbuf[0] = '\0';
113 }
114 ~pkgDPkgPMPrivate()
115 {
116 }
117 bool stdin_is_dev_null;
118 // the buffer we use for the dpkg status-fd reading
119 char dpkgbuf[1024];
120 size_t dpkgbuf_pos;
121 FILE *term_out;
122 FILE *history_out;
123 string dpkg_error;
124 APT::Progress::PackageManager *progress;
125
126 // pty stuff
127 struct termios tt;
128 bool tt_is_valid;
129 int master;
130 char * slave;
131 int protect_slave_from_dying;
132
133 // signals
134 sigset_t sigmask;
135 sigset_t original_sigmask;
136
137 bool direct_stdin;
138 };
139 /*}}}*/
140 namespace
141 {
142 // Maps the dpkg "processing" info to human readable names. Entry 0
143 // of each array is the key, entry 1 is the value.
144 const std::pair<const char *, const char *> PackageProcessingOps[] = {
145 std::make_pair("install", N_("Installing %s")),
146 // we don't care for the difference
147 std::make_pair("upgrade", N_("Installing %s")),
148 std::make_pair("configure", N_("Configuring %s")),
149 std::make_pair("remove", N_("Removing %s")),
150 std::make_pair("purge", N_("Completely removing %s")),
151 std::make_pair("disappear", N_("Noting disappearance of %s")),
152 std::make_pair("trigproc", N_("Running post-installation trigger %s"))
153 };
154
155 const std::pair<const char *, const char *> * const PackageProcessingOpsBegin = PackageProcessingOps;
156 const std::pair<const char *, const char *> * const PackageProcessingOpsEnd = PackageProcessingOps + sizeof(PackageProcessingOps) / sizeof(PackageProcessingOps[0]);
157
158 // Predicate to test whether an entry in the PackageProcessingOps
159 // array matches a string.
160 class MatchProcessingOp
161 {
162 const char *target;
163
164 public:
165 explicit MatchProcessingOp(const char *the_target)
166 : target(the_target)
167 {
168 }
169
170 bool operator()(const std::pair<const char *, const char *> &pair) const
171 {
172 return strcmp(pair.first, target) == 0;
173 }
174 };
175 }
176
177 // ionice - helper function to ionice the given PID /*{{{*/
178 /* there is no C header for ionice yet - just the syscall interface
179 so we use the binary from util-linux */
180 static bool ionice(int PID)
181 {
182 if (!FileExists("/usr/bin/ionice"))
183 return false;
184 pid_t Process = ExecFork();
185 if (Process == 0)
186 {
187 char buf[32];
188 snprintf(buf, sizeof(buf), "-p%d", PID);
189 const char *Args[4];
190 Args[0] = "/usr/bin/ionice";
191 Args[1] = "-c3";
192 Args[2] = buf;
193 Args[3] = 0;
194 execv(Args[0], (char **)Args);
195 }
196 return ExecWait(Process, "ionice");
197 }
198 /*}}}*/
199 // FindNowVersion - Helper to find a Version in "now" state /*{{{*/
200 // ---------------------------------------------------------------------
201 /* This is helpful when a package is no longer installed but has residual
202 * config files
203 */
204 static
205 pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
206 {
207 pkgCache::VerIterator Ver;
208 for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
209 for (pkgCache::VerFileIterator Vf = Ver.FileList(); Vf.end() == false; ++Vf)
210 for (pkgCache::PkgFileIterator F = Vf.File(); F.end() == false; ++F)
211 {
212 if (F.Archive() != 0 && strcmp(F.Archive(), "now") == 0)
213 return Ver;
214 }
215 return Ver;
216 }
217 /*}}}*/
218 static pkgCache::VerIterator FindToBeRemovedVersion(pkgCache::PkgIterator const &Pkg)/*{{{*/
219 {
220 auto const PV = Pkg.CurrentVer();
221 if (PV.end() == false)
222 return PV;
223 return FindNowVersion(Pkg);
224 }
225 /*}}}*/
226
227 // DPkgPM::pkgDPkgPM - Constructor /*{{{*/
228 // ---------------------------------------------------------------------
229 /* */
230 pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache)
231 : pkgPackageManager(Cache),d(new pkgDPkgPMPrivate()), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
232 {
233 }
234 /*}}}*/
235 // DPkgPM::pkgDPkgPM - Destructor /*{{{*/
236 // ---------------------------------------------------------------------
237 /* */
238 pkgDPkgPM::~pkgDPkgPM()
239 {
240 delete d;
241 }
242 /*}}}*/
243 // DPkgPM::Install - Install a package /*{{{*/
244 // ---------------------------------------------------------------------
245 /* Add an install operation to the sequence list */
246 bool pkgDPkgPM::Install(PkgIterator Pkg,string File)
247 {
248 if (File.empty() == true || Pkg.end() == true)
249 return _error->Error("Internal Error, No file name for %s",Pkg.FullName().c_str());
250
251 // If the filename string begins with DPkg::Chroot-Directory, return the
252 // substr that is within the chroot so dpkg can access it.
253 string const chrootdir = _config->FindDir("DPkg::Chroot-Directory","/");
254 if (chrootdir != "/" && File.find(chrootdir) == 0)
255 {
256 size_t len = chrootdir.length();
257 if (chrootdir.at(len - 1) == '/')
258 len--;
259 List.push_back(Item(Item::Install,Pkg,File.substr(len)));
260 }
261 else
262 List.push_back(Item(Item::Install,Pkg,File));
263
264 return true;
265 }
266 /*}}}*/
267 // DPkgPM::Configure - Configure a package /*{{{*/
268 // ---------------------------------------------------------------------
269 /* Add a configure operation to the sequence list */
270 bool pkgDPkgPM::Configure(PkgIterator Pkg)
271 {
272 if (Pkg.end() == true)
273 return false;
274
275 List.push_back(Item(Item::Configure, Pkg));
276
277 // Use triggers for config calls if we configure "smart"
278 // as otherwise Pre-Depends will not be satisfied, see #526774
279 if (_config->FindB("DPkg::TriggersPending", false) == true)
280 List.push_back(Item(Item::TriggersPending, PkgIterator()));
281
282 return true;
283 }
284 /*}}}*/
285 // DPkgPM::Remove - Remove a package /*{{{*/
286 // ---------------------------------------------------------------------
287 /* Add a remove operation to the sequence list */
288 bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge)
289 {
290 if (Pkg.end() == true)
291 return false;
292
293 if (Purge == true)
294 List.push_back(Item(Item::Purge,Pkg));
295 else
296 List.push_back(Item(Item::Remove,Pkg));
297 return true;
298 }
299 /*}}}*/
300 // DPkgPM::SendPkgInfo - Send info for install-pkgs hook /*{{{*/
301 // ---------------------------------------------------------------------
302 /* This is part of the helper script communication interface, it sends
303 very complete information down to the other end of the pipe.*/
304 bool pkgDPkgPM::SendV2Pkgs(FILE *F)
305 {
306 return SendPkgsInfo(F, 2);
307 }
308 bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
309 {
310 // This version of APT supports only v3, so don't sent higher versions
311 if (Version <= 3)
312 fprintf(F,"VERSION %u\n", Version);
313 else
314 fprintf(F,"VERSION 3\n");
315
316 /* Write out all of the configuration directives by walking the
317 configuration tree */
318 const Configuration::Item *Top = _config->Tree(0);
319 for (; Top != 0;)
320 {
321 if (Top->Value.empty() == false)
322 {
323 fprintf(F,"%s=%s\n",
324 QuoteString(Top->FullTag(),"=\"\n").c_str(),
325 QuoteString(Top->Value,"\n").c_str());
326 }
327
328 if (Top->Child != 0)
329 {
330 Top = Top->Child;
331 continue;
332 }
333
334 while (Top != 0 && Top->Next == 0)
335 Top = Top->Parent;
336 if (Top != 0)
337 Top = Top->Next;
338 }
339 fprintf(F,"\n");
340
341 // Write out the package actions in order.
342 for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
343 {
344 if(I->Pkg.end() == true)
345 continue;
346
347 pkgDepCache::StateCache &S = Cache[I->Pkg];
348
349 fprintf(F,"%s ",I->Pkg.Name());
350
351 // Current version which we are going to replace
352 pkgCache::VerIterator CurVer = I->Pkg.CurrentVer();
353 if (CurVer.end() == true && (I->Op == Item::Remove || I->Op == Item::Purge))
354 CurVer = FindNowVersion(I->Pkg);
355
356 if (CurVer.end() == true)
357 {
358 if (Version <= 2)
359 fprintf(F, "- ");
360 else
361 fprintf(F, "- - none ");
362 }
363 else
364 {
365 fprintf(F, "%s ", CurVer.VerStr());
366 if (Version >= 3)
367 fprintf(F, "%s %s ", CurVer.Arch(), CurVer.MultiArchType());
368 }
369
370 // Show the compare operator between current and install version
371 if (S.InstallVer != 0)
372 {
373 pkgCache::VerIterator const InstVer = S.InstVerIter(Cache);
374 int Comp = 2;
375 if (CurVer.end() == false)
376 Comp = InstVer.CompareVer(CurVer);
377 if (Comp < 0)
378 fprintf(F,"> ");
379 else if (Comp == 0)
380 fprintf(F,"= ");
381 else if (Comp > 0)
382 fprintf(F,"< ");
383 fprintf(F, "%s ", InstVer.VerStr());
384 if (Version >= 3)
385 fprintf(F, "%s %s ", InstVer.Arch(), InstVer.MultiArchType());
386 }
387 else
388 {
389 if (Version <= 2)
390 fprintf(F, "> - ");
391 else
392 fprintf(F, "> - - none ");
393 }
394
395 // Show the filename/operation
396 if (I->Op == Item::Install)
397 {
398 // No errors here..
399 if (I->File[0] != '/')
400 fprintf(F,"**ERROR**\n");
401 else
402 fprintf(F,"%s\n",I->File.c_str());
403 }
404 else if (I->Op == Item::Configure)
405 fprintf(F,"**CONFIGURE**\n");
406 else if (I->Op == Item::Remove ||
407 I->Op == Item::Purge)
408 fprintf(F,"**REMOVE**\n");
409
410 if (ferror(F) != 0)
411 return false;
412 }
413 return true;
414 }
415 /*}}}*/
416 // DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
417 // ---------------------------------------------------------------------
418 /* This looks for a list of scripts to run from the configuration file
419 each one is run and is fed on standard input a list of all .deb files
420 that are due to be installed. */
421 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
422 {
423 bool result = true;
424
425 Configuration::Item const *Opts = _config->Tree(Cnf);
426 if (Opts == 0 || Opts->Child == 0)
427 return true;
428 Opts = Opts->Child;
429
430 sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN);
431 sighandler_t old_sigint = signal(SIGINT, SIG_IGN);
432 sighandler_t old_sigquit = signal(SIGQUIT, SIG_IGN);
433
434 unsigned int Count = 1;
435 for (; Opts != 0; Opts = Opts->Next, Count++)
436 {
437 if (Opts->Value.empty() == true)
438 continue;
439
440 if(_config->FindB("Debug::RunScripts", false) == true)
441 std::clog << "Running external script with list of all .deb file: '"
442 << Opts->Value << "'" << std::endl;
443
444 // Determine the protocol version
445 string OptSec = Opts->Value;
446 string::size_type Pos;
447 if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
448 Pos = OptSec.length();
449 OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
450
451 unsigned int Version = _config->FindI(OptSec+"::Version",1);
452 unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
453
454 // Create the pipes
455 std::set<int> KeepFDs;
456 MergeKeepFdsFromConfiguration(KeepFDs);
457 int Pipes[2];
458 if (pipe(Pipes) != 0) {
459 result = _error->Errno("pipe","Failed to create IPC pipe to subprocess");
460 break;
461 }
462 if (InfoFD != (unsigned)Pipes[0])
463 SetCloseExec(Pipes[0],true);
464 else
465 KeepFDs.insert(Pipes[0]);
466
467
468 SetCloseExec(Pipes[1],true);
469
470 // Purified Fork for running the script
471 pid_t Process = ExecFork(KeepFDs);
472 if (Process == 0)
473 {
474 // Setup the FDs
475 dup2(Pipes[0], InfoFD);
476 SetCloseExec(STDOUT_FILENO,false);
477 SetCloseExec(STDIN_FILENO,false);
478 SetCloseExec(STDERR_FILENO,false);
479
480 string hookfd;
481 strprintf(hookfd, "%d", InfoFD);
482 setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1);
483
484 debSystem::DpkgChrootDirectory();
485 const char *Args[4];
486 Args[0] = "/bin/sh";
487 Args[1] = "-c";
488 Args[2] = Opts->Value.c_str();
489 Args[3] = 0;
490 execv(Args[0],(char **)Args);
491 _exit(100);
492 }
493 close(Pipes[0]);
494 FILE *F = fdopen(Pipes[1],"w");
495 if (F == 0) {
496 result = _error->Errno("fdopen","Failed to open new FD");
497 break;
498 }
499
500 // Feed it the filenames.
501 if (Version <= 1)
502 {
503 for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
504 {
505 // Only deal with packages to be installed from .deb
506 if (I->Op != Item::Install)
507 continue;
508
509 // No errors here..
510 if (I->File[0] != '/')
511 continue;
512
513 /* Feed the filename of each package that is pending install
514 into the pipe. */
515 fprintf(F,"%s\n",I->File.c_str());
516 if (ferror(F) != 0)
517 break;
518 }
519 }
520 else
521 SendPkgsInfo(F, Version);
522
523 fclose(F);
524
525 // Clean up the sub process
526 if (ExecWait(Process,Opts->Value.c_str()) == false) {
527 result = _error->Error("Failure running script %s",Opts->Value.c_str());
528 break;
529 }
530 }
531 signal(SIGINT, old_sigint);
532 signal(SIGPIPE, old_sigpipe);
533 signal(SIGQUIT, old_sigquit);
534
535 return result;
536 }
537 /*}}}*/
538 // DPkgPM::DoStdin - Read stdin and pass to master pty /*{{{*/
539 // ---------------------------------------------------------------------
540 /*
541 */
542 void pkgDPkgPM::DoStdin(int master)
543 {
544 unsigned char input_buf[256] = {0,};
545 ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf));
546 if (len)
547 FileFd::Write(master, input_buf, len);
548 else
549 d->stdin_is_dev_null = true;
550 }
551 /*}}}*/
552 // DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/
553 // ---------------------------------------------------------------------
554 /*
555 * read the terminal pty and write log
556 */
557 void pkgDPkgPM::DoTerminalPty(int master)
558 {
559 unsigned char term_buf[1024] = {0,0, };
560
561 ssize_t len=read(master, term_buf, sizeof(term_buf));
562 if(len == -1 && errno == EIO)
563 {
564 // this happens when the child is about to exit, we
565 // give it time to actually exit, otherwise we run
566 // into a race so we sleep for half a second.
567 struct timespec sleepfor = { 0, 500000000 };
568 nanosleep(&sleepfor, NULL);
569 return;
570 }
571 if(len <= 0)
572 return;
573 FileFd::Write(1, term_buf, len);
574 if(d->term_out)
575 fwrite(term_buf, len, sizeof(char), d->term_out);
576 }
577 /*}}}*/
578 // DPkgPM::ProcessDpkgStatusBuf /*{{{*/
579 void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
580 {
581 bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false);
582 if (Debug == true)
583 std::clog << "got from dpkg '" << line << "'" << std::endl;
584
585 /* dpkg sends strings like this:
586 'status: <pkg>: <pkg qstate>'
587 'status: <pkg>:<arch>: <pkg qstate>'
588
589 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: pkg'
590 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: trigger'
591 */
592
593 // we need to split on ": " (note the appended space) as the ':' is
594 // part of the pkgname:arch information that dpkg sends
595 //
596 // A dpkg error message may contain additional ":" (like
597 // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..."
598 // so we need to ensure to not split too much
599 std::vector<std::string> list = StringSplit(line, ": ", 4);
600 if(list.size() < 3)
601 {
602 if (Debug == true)
603 std::clog << "ignoring line: not enough ':'" << std::endl;
604 return;
605 }
606
607 // build the (prefix, pkgname, action) tuple, position of this
608 // is different for "processing" or "status" messages
609 std::string prefix = APT::String::Strip(list[0]);
610 std::string pkgname;
611 std::string action;
612
613 // "processing" has the form "processing: action: pkg or trigger"
614 // with action = ["install", "upgrade", "configure", "remove", "purge",
615 // "disappear", "trigproc"]
616 if (prefix == "processing")
617 {
618 pkgname = APT::String::Strip(list[2]);
619 action = APT::String::Strip(list[1]);
620 }
621 // "status" has the form: "status: pkg: state"
622 // with state in ["half-installed", "unpacked", "half-configured",
623 // "installed", "config-files", "not-installed"]
624 else if (prefix == "status")
625 {
626 pkgname = APT::String::Strip(list[1]);
627 action = APT::String::Strip(list[2]);
628
629 /* handle the special cases first:
630
631 errors look like this:
632 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data
633 and conffile-prompt like this
634 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited
635 */
636 if(action == "error")
637 {
638 d->progress->Error(pkgname, PackagesDone, PackagesTotal, list[3]);
639 ++pkgFailures;
640 WriteApportReport(pkgname.c_str(), list[3].c_str());
641 return;
642 }
643 else if(action == "conffile-prompt")
644 {
645 d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, list[3]);
646 return;
647 }
648 } else {
649 if (Debug == true)
650 std::clog << "unknown prefix '" << prefix << "'" << std::endl;
651 return;
652 }
653
654 // At this point we have a pkgname, but it might not be arch-qualified !
655 if (pkgname.find(":") == std::string::npos)
656 {
657 pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname);
658 /* No arch means that dpkg believes there can only be one package
659 this can refer to so lets see what could be candidates here: */
660 std::vector<pkgCache::PkgIterator> candset;
661 for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
662 {
663 if (PackageOps.find(P.FullName()) != PackageOps.end())
664 candset.push_back(P);
665 // packages can disappear without them having any interaction itself
666 // so we have to consider these as candidates, too
667 else if (P->CurrentVer != 0 && action == "disappear")
668 candset.push_back(P);
669 }
670 if (unlikely(candset.empty()))
671 {
672 if (Debug == true)
673 std::clog << "unable to figure out which package is dpkg refering to with '" << pkgname << "'! (1)" << std::endl;
674 return;
675 }
676 else if (candset.size() == 1) // we are lucky
677 pkgname = candset.cbegin()->FullName();
678 else
679 {
680 /* here be dragons^Wassumptions about dpkg:
681 - an M-A:same version is always arch-qualified
682 - a package from a foreign arch is (in newer versions) */
683 size_t installedInstances = 0, wannabeInstances = 0;
684 for (auto const &P: candset)
685 {
686 if (P->CurrentVer != 0)
687 {
688 ++installedInstances;
689 if (Cache[P].Delete() == false)
690 ++wannabeInstances;
691 }
692 else if (Cache[P].Install())
693 ++wannabeInstances;
694 }
695 // the package becomes M-A:same, so we are still talking about current
696 if (installedInstances == 1 && wannabeInstances >= 2)
697 {
698 for (auto const &P: candset)
699 {
700 if (P->CurrentVer == 0)
701 continue;
702 pkgname = P.FullName();
703 break;
704 }
705 }
706 // the package was M-A:same, it isn't now, so we can only talk about that
707 else if (installedInstances >= 2 && wannabeInstances == 1)
708 {
709 for (auto const &P: candset)
710 {
711 auto const IV = Cache[P].InstVerIter(Cache);
712 if (IV.end())
713 continue;
714 pkgname = P.FullName();
715 break;
716 }
717 }
718 // that is a crossgrade
719 else if (installedInstances == 1 && wannabeInstances == 1 && candset.size() == 2)
720 {
721 auto const PkgHasCurrentVersion = [](pkgCache::PkgIterator const &P) { return P->CurrentVer != 0; };
722 auto const P = std::find_if(candset.begin(), candset.end(), PkgHasCurrentVersion);
723 if (unlikely(P == candset.end()))
724 {
725 if (Debug == true)
726 std::clog << "situation for '" << pkgname << "' looked like a crossgrade, but no current version?!" << std::endl;
727 return;
728 }
729 auto fullname = P->FullName();
730 if (PackageOps[fullname].size() != PackageOpsDone[fullname])
731 pkgname = std::move(fullname);
732 else
733 pkgname = std::find_if_not(candset.begin(), candset.end(), PkgHasCurrentVersion)->FullName();
734 }
735 // we are desperate: so "just" take the native one, but that might change mid-air,
736 // so we have to ask dpkg what it believes native is at the moment… all the time
737 else
738 {
739 std::vector<std::string> sArgs = debSystem::GetDpkgBaseCommand();
740 sArgs.push_back("--print-architecture");
741 int outputFd = -1;
742 pid_t const dpkgNativeArch = debSystem::ExecDpkg(sArgs, nullptr, &outputFd, true);
743 if (unlikely(dpkgNativeArch == -1))
744 {
745 if (Debug == true)
746 std::clog << "calling dpkg failed to ask it for its current native architecture to expand '" << pkgname << "'!" << std::endl;
747 return;
748 }
749 FILE *dpkg = fdopen(outputFd, "r");
750 if(dpkg != NULL)
751 {
752 char* buf = NULL;
753 size_t bufsize = 0;
754 if (getline(&buf, &bufsize, dpkg) != -1)
755 pkgname += ':' + bufsize;
756 free(buf);
757 fclose(dpkg);
758 }
759 ExecWait(dpkgNativeArch, "dpkg --print-architecture", true);
760 if (pkgname.find(':') != std::string::npos)
761 {
762 if (Debug == true)
763 std::clog << "unable to figure out which package is dpkg refering to with '" << pkgname << "'! (2)" << std::endl;
764 return;
765 }
766 }
767 }
768 }
769
770 std::string arch = "";
771 if (pkgname.find(":") != string::npos)
772 arch = StringSplit(pkgname, ":")[1];
773 std::string i18n_pkgname = pkgname;
774 if (arch.size() != 0)
775 strprintf(i18n_pkgname, "%s (%s)", StringSplit(pkgname, ":")[0].c_str(), arch.c_str());
776
777 // 'processing' from dpkg looks like
778 // 'processing: action: pkg'
779 if(prefix == "processing")
780 {
781 auto const iter = std::find_if(PackageProcessingOpsBegin, PackageProcessingOpsEnd, MatchProcessingOp(action.c_str()));
782 if(iter == PackageProcessingOpsEnd)
783 {
784 if (Debug == true)
785 std::clog << "ignoring unknown action: " << action << std::endl;
786 return;
787 }
788 std::string msg;
789 strprintf(msg, _(iter->second), i18n_pkgname.c_str());
790 d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
791
792 // FIXME: this needs a muliarch testcase
793 // FIXME2: is "pkgname" here reliable with dpkg only sending us
794 // short pkgnames?
795 if (action == "disappear")
796 handleDisappearAction(pkgname);
797 else if (action == "upgrade")
798 handleCrossUpgradeAction(pkgname);
799 return;
800 }
801
802 if (prefix == "status")
803 {
804 std::vector<struct DpkgState> &states = PackageOps[pkgname];
805 if(PackageOpsDone[pkgname] < states.size())
806 {
807 char const * next_action = states[PackageOpsDone[pkgname]].state;
808 if (next_action)
809 {
810 /*
811 if (action == "half-installed" && strcmp("half-configured", next_action) == 0 &&
812 PackageOpsDone[pkg] + 2 < states.size() && action == states[PackageOpsDone[pkg] + 2].state)
813 {
814 if (Debug == true)
815 std::clog << "(parsed from dpkg) pkg: " << short_pkgname << " action: " << action
816 << " pending trigger defused by unpack" << std::endl;
817 // unpacking a package defuses the pending trigger
818 PackageOpsDone[pkg] += 2;
819 PackagesDone += 2;
820 next_action = states[PackageOpsDone[pkg]].state;
821 }
822 */
823 if (Debug == true)
824 std::clog << "(parsed from dpkg) pkg: " << pkgname
825 << " action: " << action << " (expected: '" << next_action << "' "
826 << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
827
828 // check if the package moved to the next dpkg state
829 if(action == next_action)
830 {
831 // only read the translation if there is actually a next action
832 char const * const translation = _(states[PackageOpsDone[pkgname]].str);
833
834 // we moved from one dpkg state to a new one, report that
835 ++PackageOpsDone[pkgname];
836 ++PackagesDone;
837
838 std::string msg;
839 strprintf(msg, translation, i18n_pkgname.c_str());
840 d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
841 }
842 }
843 }
844 else if (action == "triggers-pending")
845 {
846 if (Debug == true)
847 std::clog << "(parsed from dpkg) pkg: " << pkgname
848 << " action: " << action << " (prefix 2 to "
849 << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
850
851 states.insert(states.begin(), {"installed", N_("Installed %s")});
852 states.insert(states.begin(), {"half-configured", N_("Configuring %s")});
853 PackagesTotal += 2;
854 }
855 }
856 }
857 /*}}}*/
858 // DPkgPM::handleDisappearAction /*{{{*/
859 void pkgDPkgPM::handleDisappearAction(string const &pkgname)
860 {
861 pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
862 if (unlikely(Pkg.end() == true))
863 return;
864
865 // a disappeared package has no further actions
866 auto const ROps = PackageOps[Pkg.FullName()].size();
867 auto && ROpsDone = PackageOpsDone[Pkg.FullName()];
868 PackagesDone += ROps - ROpsDone;
869 ROpsDone = ROps;
870
871 // record the package name for display and stuff later
872 disappearedPkgs.insert(Pkg.FullName(true));
873
874 // the disappeared package was auto-installed - nothing to do
875 if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
876 return;
877 pkgCache::VerIterator PkgVer = Cache[Pkg].InstVerIter(Cache);
878 if (unlikely(PkgVer.end() == true))
879 return;
880 /* search in the list of dependencies for (Pre)Depends,
881 check if this dependency has a Replaces on our package
882 and if so transfer the manual installed flag to it */
883 for (pkgCache::DepIterator Dep = PkgVer.DependsList(); Dep.end() != true; ++Dep)
884 {
885 if (Dep->Type != pkgCache::Dep::Depends &&
886 Dep->Type != pkgCache::Dep::PreDepends)
887 continue;
888 pkgCache::PkgIterator Tar = Dep.TargetPkg();
889 if (unlikely(Tar.end() == true))
890 continue;
891 // the package is already marked as manual
892 if ((Cache[Tar].Flags & pkgCache::Flag::Auto) != pkgCache::Flag::Auto)
893 continue;
894 pkgCache::VerIterator TarVer = Cache[Tar].InstVerIter(Cache);
895 if (TarVer.end() == true)
896 continue;
897 for (pkgCache::DepIterator Rep = TarVer.DependsList(); Rep.end() != true; ++Rep)
898 {
899 if (Rep->Type != pkgCache::Dep::Replaces)
900 continue;
901 if (Pkg != Rep.TargetPkg())
902 continue;
903 // okay, they are strongly connected - transfer manual-bit
904 if (Debug == true)
905 std::clog << "transfer manual-bit from disappeared »" << pkgname << "« to »" << Tar.FullName() << "«" << std::endl;
906 Cache[Tar].Flags &= ~Flag::Auto;
907 break;
908 }
909 }
910 }
911 /*}}}*/
912 void pkgDPkgPM::handleCrossUpgradeAction(string const &pkgname) /*{{{*/
913 {
914 // in a crossgrade what looked like a remove first is really an unpack over it
915 auto const Pkg = Cache.FindPkg(pkgname);
916 if (likely(Pkg.end() == false) && Cache[Pkg].Delete())
917 {
918 auto const Grp = Pkg.Group();
919 if (likely(Grp.end() == false))
920 {
921 for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
922 if(Cache[P].Install())
923 {
924 auto && Ops = PackageOps[P.FullName()];
925 auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; });
926 if (unpackOp != Ops.cend())
927 {
928 // skip ahead in the crossgraded packages
929 auto const skipped = std::distance(Ops.cbegin(), unpackOp);
930 PackagesDone += skipped;
931 PackageOpsDone[P.FullName()] += skipped;
932 // finish the crossremoved package
933 auto const ROps = PackageOps[Pkg.FullName()].size();
934 auto && ROpsDone = PackageOpsDone[Pkg.FullName()];
935 PackagesDone += ROps - ROpsDone;
936 ROpsDone = ROps;
937 break;
938 }
939 }
940 }
941 }
942 }
943 /*}}}*/
944 // DPkgPM::DoDpkgStatusFd /*{{{*/
945 void pkgDPkgPM::DoDpkgStatusFd(int statusfd)
946 {
947 ssize_t const len = read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos],
948 (sizeof(d->dpkgbuf)/sizeof(d->dpkgbuf[0])) - d->dpkgbuf_pos);
949 if(len <= 0)
950 return;
951 d->dpkgbuf_pos += (len / sizeof(d->dpkgbuf[0]));
952
953 // process line by line from the buffer
954 char *p = d->dpkgbuf, *q = nullptr;
955 while((q=(char*)memchr(p, '\n', (d->dpkgbuf + d->dpkgbuf_pos) - p)) != nullptr)
956 {
957 *q = '\0';
958 ProcessDpkgStatusLine(p);
959 p = q + 1; // continue with next line
960 }
961
962 // check if we stripped the buffer clean
963 if (p > (d->dpkgbuf + d->dpkgbuf_pos))
964 {
965 d->dpkgbuf_pos = 0;
966 return;
967 }
968
969 // otherwise move the unprocessed tail to the start and update pos
970 memmove(d->dpkgbuf, p, (p - d->dpkgbuf));
971 d->dpkgbuf_pos = (d->dpkgbuf + d->dpkgbuf_pos) - p;
972 }
973 /*}}}*/
974 // DPkgPM::WriteHistoryTag /*{{{*/
975 void pkgDPkgPM::WriteHistoryTag(string const &tag, string value)
976 {
977 size_t const length = value.length();
978 if (length == 0)
979 return;
980 // poor mans rstrip(", ")
981 if (value[length-2] == ',' && value[length-1] == ' ')
982 value.erase(length - 2, 2);
983 fprintf(d->history_out, "%s: %s\n", tag.c_str(), value.c_str());
984 } /*}}}*/
985 // DPkgPM::OpenLog /*{{{*/
986 bool pkgDPkgPM::OpenLog()
987 {
988 string const logdir = _config->FindDir("Dir::Log");
989 if(CreateAPTDirectoryIfNeeded(logdir, logdir) == false)
990 // FIXME: use a better string after freeze
991 return _error->Error(_("Directory '%s' missing"), logdir.c_str());
992
993 // get current time
994 char timestr[200];
995 time_t const t = time(NULL);
996 struct tm tm_buf;
997 struct tm const * const tmp = localtime_r(&t, &tm_buf);
998 strftime(timestr, sizeof(timestr), "%F %T", tmp);
999
1000 // open terminal log
1001 string const logfile_name = flCombine(logdir,
1002 _config->Find("Dir::Log::Terminal"));
1003 if (!logfile_name.empty())
1004 {
1005 d->term_out = fopen(logfile_name.c_str(),"a");
1006 if (d->term_out == NULL)
1007 return _error->WarningE("OpenLog", _("Could not open file '%s'"), logfile_name.c_str());
1008 setvbuf(d->term_out, NULL, _IONBF, 0);
1009 SetCloseExec(fileno(d->term_out), true);
1010 if (getuid() == 0) // if we aren't root, we can't chown a file, so don't try it
1011 {
1012 struct passwd *pw = getpwnam("root");
1013 struct group *gr = getgrnam("adm");
1014 if (pw != NULL && gr != NULL && chown(logfile_name.c_str(), pw->pw_uid, gr->gr_gid) != 0)
1015 _error->WarningE("OpenLog", "chown to root:adm of file %s failed", logfile_name.c_str());
1016 }
1017 if (chmod(logfile_name.c_str(), 0640) != 0)
1018 _error->WarningE("OpenLog", "chmod 0640 of file %s failed", logfile_name.c_str());
1019 fprintf(d->term_out, "\nLog started: %s\n", timestr);
1020 }
1021
1022 // write your history
1023 string const history_name = flCombine(logdir,
1024 _config->Find("Dir::Log::History"));
1025 if (!history_name.empty())
1026 {
1027 d->history_out = fopen(history_name.c_str(),"a");
1028 if (d->history_out == NULL)
1029 return _error->WarningE("OpenLog", _("Could not open file '%s'"), history_name.c_str());
1030 SetCloseExec(fileno(d->history_out), true);
1031 chmod(history_name.c_str(), 0644);
1032 fprintf(d->history_out, "\nStart-Date: %s\n", timestr);
1033 string remove, purge, install, reinstall, upgrade, downgrade;
1034 for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I)
1035 {
1036 enum { CANDIDATE, CANDIDATE_AUTO, CURRENT_CANDIDATE, CURRENT } infostring;
1037 string *line = NULL;
1038 #define HISTORYINFO(X, Y) { line = &X; infostring = Y; }
1039 if (Cache[I].NewInstall() == true)
1040 HISTORYINFO(install, CANDIDATE_AUTO)
1041 else if (Cache[I].ReInstall() == true)
1042 HISTORYINFO(reinstall, CANDIDATE)
1043 else if (Cache[I].Upgrade() == true)
1044 HISTORYINFO(upgrade, CURRENT_CANDIDATE)
1045 else if (Cache[I].Downgrade() == true)
1046 HISTORYINFO(downgrade, CURRENT_CANDIDATE)
1047 else if (Cache[I].Delete() == true)
1048 HISTORYINFO((Cache[I].Purge() ? purge : remove), CURRENT)
1049 else
1050 continue;
1051 #undef HISTORYINFO
1052 line->append(I.FullName(false)).append(" (");
1053 switch (infostring) {
1054 case CANDIDATE: line->append(Cache[I].CandVersion); break;
1055 case CANDIDATE_AUTO:
1056 line->append(Cache[I].CandVersion);
1057 if ((Cache[I].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
1058 line->append(", automatic");
1059 break;
1060 case CURRENT_CANDIDATE: line->append(Cache[I].CurVersion).append(", ").append(Cache[I].CandVersion); break;
1061 case CURRENT: line->append(Cache[I].CurVersion); break;
1062 }
1063 line->append("), ");
1064 }
1065 if (_config->Exists("Commandline::AsString") == true)
1066 WriteHistoryTag("Commandline", _config->Find("Commandline::AsString"));
1067 std::string RequestingUser = AptHistoryRequestingUser();
1068 if (RequestingUser != "")
1069 WriteHistoryTag("Requested-By", RequestingUser);
1070 WriteHistoryTag("Install", install);
1071 WriteHistoryTag("Reinstall", reinstall);
1072 WriteHistoryTag("Upgrade", upgrade);
1073 WriteHistoryTag("Downgrade",downgrade);
1074 WriteHistoryTag("Remove",remove);
1075 WriteHistoryTag("Purge",purge);
1076 fflush(d->history_out);
1077 }
1078
1079 return true;
1080 }
1081 /*}}}*/
1082 // DPkg::CloseLog /*{{{*/
1083 bool pkgDPkgPM::CloseLog()
1084 {
1085 char timestr[200];
1086 time_t t = time(NULL);
1087 struct tm tm_buf;
1088 struct tm *tmp = localtime_r(&t, &tm_buf);
1089 strftime(timestr, sizeof(timestr), "%F %T", tmp);
1090
1091 if(d->term_out)
1092 {
1093 fprintf(d->term_out, "Log ended: ");
1094 fprintf(d->term_out, "%s", timestr);
1095 fprintf(d->term_out, "\n");
1096 fclose(d->term_out);
1097 }
1098 d->term_out = NULL;
1099
1100 if(d->history_out)
1101 {
1102 if (disappearedPkgs.empty() == false)
1103 {
1104 string disappear;
1105 for (std::set<std::string>::const_iterator d = disappearedPkgs.begin();
1106 d != disappearedPkgs.end(); ++d)
1107 {
1108 pkgCache::PkgIterator P = Cache.FindPkg(*d);
1109 disappear.append(*d);
1110 if (P.end() == true)
1111 disappear.append(", ");
1112 else
1113 disappear.append(" (").append(Cache[P].CurVersion).append("), ");
1114 }
1115 WriteHistoryTag("Disappeared", disappear);
1116 }
1117 if (d->dpkg_error.empty() == false)
1118 fprintf(d->history_out, "Error: %s\n", d->dpkg_error.c_str());
1119 fprintf(d->history_out, "End-Date: %s\n", timestr);
1120 fclose(d->history_out);
1121 }
1122 d->history_out = NULL;
1123
1124 return true;
1125 }
1126 /*}}}*/
1127
1128 // DPkgPM::BuildPackagesProgressMap /*{{{*/
1129 void pkgDPkgPM::BuildPackagesProgressMap()
1130 {
1131 // map the dpkg states to the operations that are performed
1132 // (this is sorted in the same way as Item::Ops)
1133 static const std::array<std::array<DpkgState, 3>, 4> DpkgStatesOpMap = {{
1134 // Install operation
1135 {{
1136 {"half-installed", N_("Preparing %s")},
1137 {"unpacked", N_("Unpacking %s") },
1138 {nullptr, nullptr}
1139 }},
1140 // Configure operation
1141 {{
1142 {"unpacked",N_("Preparing to configure %s") },
1143 {"half-configured", N_("Configuring %s") },
1144 { "installed", N_("Installed %s")},
1145 }},
1146 // Remove operation
1147 {{
1148 {"half-configured", N_("Preparing for removal of %s")},
1149 {"half-installed", N_("Removing %s")},
1150 {"config-files", N_("Removed %s")},
1151 }},
1152 // Purge operation
1153 {{
1154 {"config-files", N_("Preparing to completely remove %s")},
1155 {"not-installed", N_("Completely removed %s")},
1156 {nullptr, nullptr}
1157 }},
1158 }};
1159 static_assert(Item::Purge == 3, "Enum item has unexpected index for mapping array");
1160
1161 // init the PackageOps map, go over the list of packages that
1162 // that will be [installed|configured|removed|purged] and add
1163 // them to the PackageOps map (the dpkg states it goes through)
1164 // and the PackageOpsTranslations (human readable strings)
1165 for (auto &&I : List)
1166 {
1167 if(I.Pkg.end() == true)
1168 continue;
1169
1170 string const name = I.Pkg.FullName();
1171 PackageOpsDone[name] = 0;
1172 auto AddToPackageOps = std::back_inserter(PackageOps[name]);
1173 if (I.Op == Item::Purge && I.Pkg->CurrentVer != 0)
1174 {
1175 // purging a package which is installed first passes through remove states
1176 auto const DpkgOps = DpkgStatesOpMap[Item::Remove];
1177 std::copy(DpkgOps.begin(), DpkgOps.end(), AddToPackageOps);
1178 PackagesTotal += DpkgOps.size();
1179 }
1180 auto const DpkgOps = DpkgStatesOpMap[I.Op];
1181 std::copy_if(DpkgOps.begin(), DpkgOps.end(), AddToPackageOps, [&](DpkgState const &state) {
1182 if (state.state == nullptr)
1183 return false;
1184 ++PackagesTotal;
1185 return true;
1186 });
1187 if ((I.Op == Item::Remove || I.Op == Item::Purge) && I.Pkg->CurrentVer != 0)
1188 {
1189 if (I.Pkg->CurrentState == pkgCache::State::UnPacked ||
1190 I.Pkg->CurrentState == pkgCache::State::HalfInstalled)
1191 {
1192 if (likely(strcmp(PackageOps[name][0].state, "half-configured") == 0))
1193 {
1194 ++PackageOpsDone[name];
1195 --PackagesTotal;
1196 }
1197 }
1198 }
1199 }
1200 /* one extra: We don't want the progress bar to reach 100%, especially not
1201 if we call dpkg --configure --pending and process a bunch of triggers
1202 while showing 100%. Also, spindown takes a while, so never reaching 100%
1203 is way more correct than reaching 100% while still doing stuff even if
1204 doing it this way is slightly bending the rules */
1205 ++PackagesTotal;
1206 }
1207 /*}}}*/
1208 bool pkgDPkgPM::Go(int StatusFd) /*{{{*/
1209 {
1210 APT::Progress::PackageManager *progress = NULL;
1211 if (StatusFd == -1)
1212 progress = APT::Progress::PackageManagerProgressFactory();
1213 else
1214 progress = new APT::Progress::PackageManagerProgressFd(StatusFd);
1215
1216 return Go(progress);
1217 }
1218 /*}}}*/
1219 void pkgDPkgPM::StartPtyMagic() /*{{{*/
1220 {
1221 if (_config->FindB("Dpkg::Use-Pty", true) == false)
1222 {
1223 d->master = -1;
1224 if (d->slave != NULL)
1225 free(d->slave);
1226 d->slave = NULL;
1227 return;
1228 }
1229
1230 if (isatty(STDIN_FILENO) == 0)
1231 d->direct_stdin = true;
1232
1233 _error->PushToStack();
1234
1235 d->master = posix_openpt(O_RDWR | O_NOCTTY);
1236 if (d->master == -1)
1237 _error->Errno("posix_openpt", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
1238 else if (unlockpt(d->master) == -1)
1239 _error->Errno("unlockpt", "Unlocking the slave of master fd %d failed!", d->master);
1240 else
1241 {
1242 #ifdef HAVE_PTSNAME_R
1243 char slave_name[64]; // 64 is used by bionic
1244 if (ptsname_r(d->master, slave_name, sizeof(slave_name)) != 0)
1245 #else
1246 char const * const slave_name = ptsname(d->master);
1247 if (slave_name == NULL)
1248 #endif
1249 _error->Errno("ptsname", "Getting name for slave of master fd %d failed!", d->master);
1250 else
1251 {
1252 d->slave = strdup(slave_name);
1253 if (d->slave == NULL)
1254 _error->Errno("strdup", "Copying name %s for slave of master fd %d failed!", slave_name, d->master);
1255 else if (grantpt(d->master) == -1)
1256 _error->Errno("grantpt", "Granting access to slave %s based on master fd %d failed!", slave_name, d->master);
1257 else if (tcgetattr(STDIN_FILENO, &d->tt) == 0)
1258 {
1259 d->tt_is_valid = true;
1260 struct termios raw_tt;
1261 // copy window size of stdout if its a 'good' terminal
1262 if (tcgetattr(STDOUT_FILENO, &raw_tt) == 0)
1263 {
1264 struct winsize win;
1265 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0)
1266 _error->Errno("ioctl", "Getting TIOCGWINSZ from stdout failed!");
1267 if (ioctl(d->master, TIOCSWINSZ, &win) < 0)
1268 _error->Errno("ioctl", "Setting TIOCSWINSZ for master fd %d failed!", d->master);
1269 }
1270 if (tcsetattr(d->master, TCSANOW, &d->tt) == -1)
1271 _error->Errno("tcsetattr", "Setting in Start via TCSANOW for master fd %d failed!", d->master);
1272
1273 raw_tt = d->tt;
1274 cfmakeraw(&raw_tt);
1275 raw_tt.c_lflag &= ~ECHO;
1276 raw_tt.c_lflag |= ISIG;
1277 // block SIGTTOU during tcsetattr to prevent a hang if
1278 // the process is a member of the background process group
1279 // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html
1280 sigemptyset(&d->sigmask);
1281 sigaddset(&d->sigmask, SIGTTOU);
1282 sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask);
1283 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_tt) == -1)
1284 _error->Errno("tcsetattr", "Setting in Start via TCSAFLUSH for stdin failed!");
1285 sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL);
1286
1287 }
1288 if (d->slave != NULL)
1289 {
1290 /* on linux, closing (and later reopening) all references to the slave
1291 makes the slave a death end, so we open it here to have one open all
1292 the time. We could use this fd in SetupSlavePtyMagic() for linux, but
1293 on kfreebsd we get an incorrect ("step like") output then while it has
1294 no problem with closing all references… so to avoid platform specific
1295 code here we combine both and be happy once more */
1296 d->protect_slave_from_dying = open(d->slave, O_RDWR | O_CLOEXEC | O_NOCTTY);
1297 }
1298 }
1299 }
1300
1301 if (_error->PendingError() == true)
1302 {
1303 if (d->master != -1)
1304 {
1305 close(d->master);
1306 d->master = -1;
1307 }
1308 if (d->slave != NULL)
1309 {
1310 free(d->slave);
1311 d->slave = NULL;
1312 }
1313 _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
1314 }
1315 _error->RevertToStack();
1316 }
1317 /*}}}*/
1318 void pkgDPkgPM::SetupSlavePtyMagic() /*{{{*/
1319 {
1320 if(d->master == -1 || d->slave == NULL)
1321 return;
1322
1323 if (close(d->master) == -1)
1324 _error->FatalE("close", "Closing master %d in child failed!", d->master);
1325 d->master = -1;
1326 if (setsid() == -1)
1327 _error->FatalE("setsid", "Starting a new session for child failed!");
1328
1329 int const slaveFd = open(d->slave, O_RDWR | O_NOCTTY);
1330 if (slaveFd == -1)
1331 _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
1332 else if (ioctl(slaveFd, TIOCSCTTY, 0) < 0)
1333 _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd);
1334 else
1335 {
1336 unsigned short i = 0;
1337 if (d->direct_stdin == true)
1338 ++i;
1339 for (; i < 3; ++i)
1340 if (dup2(slaveFd, i) == -1)
1341 _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i);
1342
1343 if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSANOW, &d->tt) < 0)
1344 _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd);
1345 }
1346
1347 if (slaveFd != -1)
1348 close(slaveFd);
1349 }
1350 /*}}}*/
1351 void pkgDPkgPM::StopPtyMagic() /*{{{*/
1352 {
1353 if (d->slave != NULL)
1354 free(d->slave);
1355 d->slave = NULL;
1356 if (d->protect_slave_from_dying != -1)
1357 {
1358 close(d->protect_slave_from_dying);
1359 d->protect_slave_from_dying = -1;
1360 }
1361 if(d->master >= 0)
1362 {
1363 if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSAFLUSH, &d->tt) == -1)
1364 _error->FatalE("tcsetattr", "Setting in Stop via TCSAFLUSH for stdin failed!");
1365 close(d->master);
1366 d->master = -1;
1367 }
1368 }
1369 /*}}}*/
1370 static void cleanUpTmpDir(char * const tmpdir) /*{{{*/
1371 {
1372 if (tmpdir == nullptr)
1373 return;
1374 DIR * const D = opendir(tmpdir);
1375 if (D == nullptr)
1376 _error->Errno("opendir", _("Unable to read %s"), tmpdir);
1377 else
1378 {
1379 auto const dfd = dirfd(D);
1380 for (struct dirent *Ent = readdir(D); Ent != nullptr; Ent = readdir(D))
1381 {
1382 if (Ent->d_name[0] == '.')
1383 continue;
1384 #ifdef _DIRENT_HAVE_D_TYPE
1385 if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN))
1386 continue;
1387 #endif
1388 char path[strlen(tmpdir) + 1 + strlen(Ent->d_name) + 1];
1389 sprintf(path, "%s/%s", tmpdir, Ent->d_name);
1390 if (unlikely(unlink(path) != 0))
1391 break;
1392 }
1393 closedir(D);
1394 rmdir(tmpdir);
1395 }
1396 free(tmpdir);
1397 }
1398 /*}}}*/
1399
1400 // DPkgPM::Go - Run the sequence /*{{{*/
1401 // ---------------------------------------------------------------------
1402 /* This globs the operations and calls dpkg
1403 *
1404 * If it is called with a progress object apt will report the install
1405 * progress to this object. It maps the dpkg states a package goes
1406 * through to human readable (and i10n-able)
1407 * names and calculates a percentage for each step.
1408 */
1409 static bool ItemIsEssential(pkgDPkgPM::Item const &I)
1410 {
1411 static auto const cachegen = _config->Find("pkgCacheGen::Essential");
1412 if (cachegen == "none" || cachegen == "native")
1413 return true;
1414 if (unlikely(I.Pkg.end()))
1415 return true;
1416 return (I.Pkg->Flags & pkgCache::Flag::Essential) != 0;
1417 }
1418 bool pkgDPkgPM::ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache)
1419 {
1420 {
1421 std::unordered_set<decltype(pkgCache::Package::ID)> alreadyRemoved;
1422 for (auto && I : List)
1423 if (I.Op == Item::Remove || I.Op == Item::Purge)
1424 alreadyRemoved.insert(I.Pkg->ID);
1425 std::remove_reference<decltype(List)>::type AppendList;
1426 for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
1427 if (Cache[Pkg].Delete() && alreadyRemoved.insert(Pkg->ID).second == true)
1428 AppendList.emplace_back(Cache[Pkg].Purge() ? Item::Purge : Item::Remove, Pkg);
1429 std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
1430 }
1431 {
1432 std::unordered_set<decltype(pkgCache::Package::ID)> alreadyConfigured;
1433 for (auto && I : List)
1434 if (I.Op == Item::Configure)
1435 alreadyConfigured.insert(I.Pkg->ID);
1436 std::remove_reference<decltype(List)>::type AppendList;
1437 for (auto && I : List)
1438 if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true)
1439 AppendList.emplace_back(Item::Configure, I.Pkg);
1440 for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
1441 if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure &&
1442 Cache[Pkg].Delete() == false && alreadyConfigured.insert(Pkg->ID).second == true)
1443 AppendList.emplace_back(Item::Configure, Pkg);
1444 std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
1445 }
1446 return true;
1447 }
1448 bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
1449 {
1450 // explicitely remove&configure everything for hookscripts and progress building
1451 // we need them only temporarily through, so keep the length and erase afterwards
1452 decltype(List)::const_iterator::difference_type explicitIdx =
1453 std::distance(List.cbegin(), List.cend());
1454 ExpandPendingCalls(List, Cache);
1455
1456 /* if dpkg told us that it has already done everything to the package we wanted it to do,
1457 we shouldn't ask it for "more" later. That can e.g. happen if packages without conffiles
1458 are purged as they will have pass through the purge states on remove already */
1459 auto const StripAlreadyDoneFrom = [&](APT::VersionVector & Pending) {
1460 Pending.erase(std::remove_if(Pending.begin(), Pending.end(), [&](pkgCache::VerIterator const &Ver) {
1461 auto const PN = Ver.ParentPkg().FullName();
1462 auto const POD = PackageOpsDone.find(PN);
1463 if (POD == PackageOpsDone.end())
1464 return false;
1465 return PackageOps[PN].size() <= POD->second;
1466 }), Pending.end());
1467 };
1468
1469 pkgPackageManager::SigINTStop = false;
1470 d->progress = progress;
1471
1472 // Generate the base argument list for dpkg
1473 std::vector<std::string> const sArgs = debSystem::GetDpkgBaseCommand();
1474 std::vector<const char *> Args(sArgs.size(), NULL);
1475 std::transform(sArgs.begin(), sArgs.end(), Args.begin(),
1476 [](std::string const &s) { return s.c_str(); });
1477 unsigned long long const StartSize = std::accumulate(sArgs.begin(), sArgs.end(), 0llu,
1478 [](unsigned long long const i, std::string const &s) { return i + s.length(); });
1479 size_t const BaseArgs = Args.size();
1480
1481 fd_set rfds;
1482 struct timespec tv;
1483
1484 // try to figure out the max environment size
1485 int OSArgMax = sysconf(_SC_ARG_MAX);
1486 if(OSArgMax < 0)
1487 OSArgMax = 32*1024;
1488 OSArgMax -= EnvironmentSize() - 2*1024;
1489 unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax);
1490 bool const NoTriggers = _config->FindB("DPkg::NoTriggers", true);
1491
1492 if (RunScripts("DPkg::Pre-Invoke") == false)
1493 return false;
1494
1495 if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
1496 return false;
1497
1498 auto const noopDPkgInvocation = _config->FindB("Debug::pkgDPkgPM",false);
1499 // store auto-bits as they are supposed to be after dpkg is run
1500 if (noopDPkgInvocation == false)
1501 Cache.writeStateFile(NULL);
1502
1503 bool dpkg_recursive_install = _config->FindB("dpkg::install::recursive", false);
1504 if (_config->FindB("dpkg::install::recursive::force", false) == false)
1505 {
1506 // dpkg uses a sorted treewalk since that version which enables the workaround to work
1507 auto const dpkgpkg = Cache.FindPkg("dpkg");
1508 if (likely(dpkgpkg.end() == false && dpkgpkg->CurrentVer != 0))
1509 dpkg_recursive_install = Cache.VS().CmpVersion("1.18.5", dpkgpkg.CurrentVer().VerStr()) <= 0;
1510 }
1511 // no point in doing this dance for a handful of packages only
1512 unsigned int const dpkg_recursive_install_min = _config->FindB("dpkg::install::recursive::minimum", 5);
1513 // FIXME: workaround for dpkg bug, see our ./test-bug-740843-versioned-up-down-breaks test
1514 bool const dpkg_recursive_install_numbered = _config->FindB("dpkg::install::recursive::numbered", true);
1515
1516 // for the progress
1517 BuildPackagesProgressMap();
1518
1519 APT::StateChanges approvedStates;
1520 if (_config->FindB("dpkg::selection::remove::approved", true))
1521 {
1522 for (auto && I : List)
1523 if (I.Op == Item::Purge)
1524 approvedStates.Purge(FindToBeRemovedVersion(I.Pkg));
1525 else if (I.Op == Item::Remove)
1526 approvedStates.Remove(FindToBeRemovedVersion(I.Pkg));
1527 }
1528
1529 // Skip removes if we install another architecture of this package soon (crossgrade)
1530 // We can't just skip them all the time as it could be an ordering requirement [of another package]
1531 if ((approvedStates.Remove().empty() == false || approvedStates.Purge().empty() == false) &&
1532 _config->FindB("dpkg::remove::crossgrade::implicit", true) == true)
1533 {
1534 std::unordered_set<decltype(pkgCache::Package::ID)> crossgraded;
1535 std::vector<std::pair<Item*, std::string>> toCrossgrade;
1536 auto const PlanedEnd = std::next(List.begin(), explicitIdx);
1537 for (auto I = List.begin(); I != PlanedEnd; ++I)
1538 {
1539 if (I->Op != Item::Remove && I->Op != Item::Purge)
1540 continue;
1541
1542 auto const Grp = I->Pkg.Group();
1543 size_t installedInstances = 0, wannabeInstances = 0;
1544 bool multiArchInstances = false;
1545 for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
1546 {
1547 if (Pkg->CurrentVer != 0)
1548 {
1549 ++installedInstances;
1550 if (Cache[Pkg].Delete() == false)
1551 ++wannabeInstances;
1552 }
1553 else if (PackageOps.find(Pkg.FullName()) != PackageOps.end())
1554 ++wannabeInstances;
1555 if (multiArchInstances == false)
1556 {
1557 auto const V = Cache[Pkg].InstVerIter(Cache);
1558 if (V.end() == false && (Pkg->CurrentVer == 0 || V != Pkg.CurrentVer()))
1559 multiArchInstances = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same);
1560 }
1561 }
1562 /* theoretically the installed check would be enough as some wannabe will
1563 be first and hence be the crossgrade we were looking for, but #844300
1564 prevents this so we keep these situations explicit removes.
1565 It is also the reason why neither of them can be a M-A:same package */
1566 if (installedInstances == 1 && wannabeInstances == 1 && multiArchInstances == false)
1567 {
1568 auto const FirstInstall = std::find_if_not(I, List.end(),
1569 [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; });
1570 auto const LastInstall = std::find_if_not(FirstInstall, List.end(),
1571 [](Item const &i) { return i.Op == Item::Install; });
1572 auto const crosser = std::find_if(FirstInstall, LastInstall,
1573 [&I](Item const &i) { return i.Pkg->Group == I->Pkg->Group; });
1574 if (crosser != LastInstall)
1575 {
1576 crossgraded.insert(I->Pkg->ID);
1577 toCrossgrade.emplace_back(&(*I), crosser->Pkg.FullName());
1578 }
1579 }
1580 }
1581 for (auto I = PlanedEnd; I != List.end(); ++I)
1582 {
1583 if (I->Op != Item::Remove && I->Op != Item::Purge)
1584 continue;
1585
1586 auto const Grp = I->Pkg.Group();
1587 for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
1588 {
1589 if (Pkg == I->Pkg || Cache[Pkg].Install() == false)
1590 continue;
1591 toCrossgrade.emplace_back(&(*I), Pkg.FullName());
1592 break;
1593 }
1594 }
1595 for (auto C : toCrossgrade)
1596 {
1597 // we never do purges on packages which are crossgraded, even if "requested"
1598 if (C.first->Op == Item::Purge)
1599 {
1600 C.first->Op = Item::Remove; // crossgrades should never be purged
1601 auto && Purges = approvedStates.Purge();
1602 auto const Ver = std::find_if(
1603 #if __GNUC__ >= 5 || (__GNUC_MINOR__ >= 9 && __GNUC__ >= 4)
1604 Purges.cbegin(), Purges.cend(),
1605 #else
1606 Purges.begin(), Purges.end(),
1607 #endif
1608 [&C](pkgCache::VerIterator const &V) { return V.ParentPkg() == C.first->Pkg; });
1609 approvedStates.Remove(*Ver);
1610 Purges.erase(Ver);
1611 auto && RemOp = PackageOps[C.first->Pkg.FullName()];
1612 if (RemOp.size() == 5)
1613 {
1614 RemOp.erase(std::next(RemOp.begin(), 3), RemOp.end());
1615 PackagesTotal -= 2;
1616 }
1617 else
1618 _error->Warning("Unexpected amount of planned ops for package %s: %lu", C.first->Pkg.FullName().c_str(), RemOp.size());
1619 }
1620 }
1621 if (crossgraded.empty() == false)
1622 {
1623 auto const oldsize = List.size();
1624 List.erase(std::remove_if(List.begin(), PlanedEnd,
1625 [&crossgraded](Item const &i){
1626 return (i.Op == Item::Remove || i.Op == Item::Purge) &&
1627 crossgraded.find(i.Pkg->ID) != crossgraded.end();
1628 }), PlanedEnd);
1629 explicitIdx -= (oldsize - List.size());
1630 }
1631 }
1632
1633 APT::StateChanges currentStates;
1634 if (_config->FindB("dpkg::selection::current::saveandrestore", true))
1635 {
1636 for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
1637 if (Pkg->CurrentVer == 0)
1638 continue;
1639 else if (Pkg->SelectedState == pkgCache::State::Purge)
1640 currentStates.Purge(FindToBeRemovedVersion(Pkg));
1641 else if (Pkg->SelectedState == pkgCache::State::DeInstall)
1642 currentStates.Remove(FindToBeRemovedVersion(Pkg));
1643 if (currentStates.empty() == false)
1644 {
1645 APT::StateChanges cleanStates;
1646 for (auto && P: currentStates.Remove())
1647 cleanStates.Install(P);
1648 for (auto && P: currentStates.Purge())
1649 cleanStates.Install(P);
1650 if (cleanStates.Save(false) == false)
1651 return _error->Error("Couldn't clean the currently selected dpkg states");
1652 }
1653 }
1654
1655 if (_config->FindB("dpkg::selection::remove::approved", true))
1656 {
1657 if (approvedStates.Save(false) == false)
1658 {
1659 _error->Error("Couldn't record the approved state changes as dpkg selection states");
1660 if (currentStates.Save(false) == false)
1661 _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
1662 return false;
1663 }
1664
1665 List.erase(std::next(List.begin(), explicitIdx), List.end());
1666
1667 std::vector<bool> toBeRemoved(Cache.Head().PackageCount, false);
1668 for (auto && I: approvedStates.Remove())
1669 toBeRemoved[I.ParentPkg()->ID] = true;
1670 for (auto && I: approvedStates.Purge())
1671 toBeRemoved[I.ParentPkg()->ID] = true;
1672
1673 for (auto && I: List)
1674 if (I.Op == Item::Remove || I.Op == Item::Purge)
1675 toBeRemoved[I.Pkg->ID] = false;
1676
1677 bool const RemovePending = std::find(toBeRemoved.begin(), toBeRemoved.end(), true) != toBeRemoved.end();
1678 bool const PurgePending = approvedStates.Purge().empty() == false;
1679 if (RemovePending != false || PurgePending != false)
1680 List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
1681 if (RemovePending)
1682 List.emplace_back(Item::RemovePending, pkgCache::PkgIterator());
1683 if (PurgePending)
1684 List.emplace_back(Item::PurgePending, pkgCache::PkgIterator());
1685
1686 // support subpressing of triggers processing for special
1687 // cases like d-i that runs the triggers handling manually
1688 if (_config->FindB("DPkg::ConfigurePending", true))
1689 List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
1690 }
1691 bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
1692
1693 d->stdin_is_dev_null = false;
1694
1695 // create log
1696 OpenLog();
1697
1698 bool dpkgMultiArch = debSystem::SupportsMultiArch();
1699
1700 // start pty magic before the loop
1701 StartPtyMagic();
1702
1703 // Tell the progress that its starting and fork dpkg
1704 d->progress->Start(d->master);
1705
1706 // this loop is runs once per dpkg operation
1707 vector<Item>::const_iterator I = List.cbegin();
1708 while (I != List.end())
1709 {
1710 // Do all actions with the same Op in one run
1711 vector<Item>::const_iterator J = I;
1712 if (TriggersPending == true)
1713 for (; J != List.end(); ++J)
1714 {
1715 if (J->Op == I->Op)
1716 continue;
1717 if (J->Op != Item::TriggersPending)
1718 break;
1719 vector<Item>::const_iterator T = J + 1;
1720 if (T != List.end() && T->Op == I->Op)
1721 continue;
1722 break;
1723 }
1724 else if (J->Op == Item::Remove || J->Op == Item::Purge)
1725 J = std::find_if(J, List.cend(), [](Item const &I) { return I.Op != Item::Remove && I.Op != Item::Purge; });
1726 else
1727 J = std::find_if(J, List.cend(), [&J](Item const &I) { return I.Op != J->Op; });
1728
1729 auto const size = (J - I) + 10;
1730
1731 // start with the baseset of arguments
1732 auto Size = StartSize;
1733 Args.erase(Args.begin() + BaseArgs, Args.end());
1734 Args.reserve(size);
1735 // keep track of allocated strings for multiarch package names
1736 std::vector<char *> Packages(size, nullptr);
1737
1738 int fd[2];
1739 if (pipe(fd) != 0)
1740 return _error->Errno("pipe","Failed to create IPC pipe to dpkg");
1741
1742 #define ADDARG(X) Args.push_back(X); Size += strlen(X)
1743 #define ADDARGC(X) Args.push_back(X); Size += sizeof(X) - 1
1744
1745 ADDARGC("--status-fd");
1746 char status_fd_buf[20];
1747 snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]);
1748 ADDARG(status_fd_buf);
1749 unsigned long const Op = I->Op;
1750
1751 if (NoTriggers == true && I->Op != Item::TriggersPending &&
1752 (I->Op != Item::ConfigurePending || std::next(I) != List.end()))
1753 {
1754 ADDARGC("--no-triggers");
1755 }
1756
1757 switch (I->Op)
1758 {
1759 case Item::Remove:
1760 case Item::Purge:
1761 ADDARGC("--force-depends");
1762 if (std::any_of(I, J, ItemIsEssential))
1763 ADDARGC("--force-remove-essential");
1764 ADDARGC("--remove");
1765 break;
1766
1767 case Item::Configure:
1768 ADDARGC("--configure");
1769 break;
1770
1771 case Item::ConfigurePending:
1772 ADDARGC("--configure");
1773 ADDARGC("--pending");
1774 break;
1775
1776 case Item::TriggersPending:
1777 ADDARGC("--triggers-only");
1778 ADDARGC("--pending");
1779 break;
1780
1781 case Item::RemovePending:
1782 ADDARGC("--remove");
1783 ADDARGC("--pending");
1784 break;
1785
1786 case Item::PurgePending:
1787 ADDARGC("--purge");
1788 ADDARGC("--pending");
1789 break;
1790
1791 case Item::Install:
1792 ADDARGC("--unpack");
1793 ADDARGC("--auto-deconfigure");
1794 break;
1795 }
1796
1797 char * tmpdir_to_free = nullptr;
1798
1799 // Write in the file or package names
1800 if (I->Op == Item::Install)
1801 {
1802 auto const installsToDo = J - I;
1803 if (dpkg_recursive_install == true && dpkg_recursive_install_min < installsToDo)
1804 {
1805 std::string tmpdir;
1806 strprintf(tmpdir, "%s/apt-dpkg-install-XXXXXX", GetTempDir().c_str());
1807 tmpdir_to_free = strndup(tmpdir.data(), tmpdir.length());
1808 if (mkdtemp(tmpdir_to_free) == nullptr)
1809 return _error->Errno("DPkg::Go", "mkdtemp of %s failed in preparation of calling dpkg unpack", tmpdir_to_free);
1810
1811 char p = 1;
1812 for (auto c = installsToDo - 1; (c = c/10) != 0; ++p);
1813 for (unsigned long n = 0; I != J; ++n, ++I)
1814 {
1815 if (I->File[0] != '/')
1816 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
1817 auto file = flNotDir(I->File);
1818 if (flExtension(file) != "deb")
1819 file.append(".deb");
1820 std::string linkpath;
1821 if (dpkg_recursive_install_numbered)
1822 strprintf(linkpath, "%s/%.*lu-%s", tmpdir_to_free, p, n, file.c_str());
1823 else
1824 strprintf(linkpath, "%s/%s", tmpdir_to_free, file.c_str());
1825 if (symlink(I->File.c_str(), linkpath.c_str()) != 0)
1826 return _error->Errno("DPkg::Go", "Symlinking %s to %s failed!", I->File.c_str(), linkpath.c_str());
1827 }
1828 ADDARGC("--recursive");
1829 ADDARG(tmpdir_to_free);
1830 }
1831 else
1832 {
1833 for (;I != J && Size < MaxArgBytes; ++I)
1834 {
1835 if (I->File[0] != '/')
1836 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
1837 Args.push_back(I->File.c_str());
1838 Size += I->File.length();
1839 }
1840 }
1841 }
1842 else if (I->Op == Item::RemovePending)
1843 {
1844 ++I;
1845 StripAlreadyDoneFrom(approvedStates.Remove());
1846 if (approvedStates.Remove().empty())
1847 continue;
1848 }
1849 else if (I->Op == Item::PurgePending)
1850 {
1851 ++I;
1852 // explicit removes of packages without conffiles passthrough the purge states instantly, too.
1853 // Setting these non-installed packages up for purging generates 'unknown pkg' warnings from dpkg
1854 StripAlreadyDoneFrom(approvedStates.Purge());
1855 if (approvedStates.Purge().empty())
1856 continue;
1857 std::remove_reference<decltype(approvedStates.Remove())>::type approvedRemoves;
1858 std::swap(approvedRemoves, approvedStates.Remove());
1859 // we apply it again here as an explicit remove in the ordering will have cleared the purge state
1860 if (approvedStates.Save(false) == false)
1861 {
1862 _error->Error("Couldn't record the approved purges as dpkg selection states");
1863 if (currentStates.Save(false) == false)
1864 _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
1865 return false;
1866 }
1867 std::swap(approvedRemoves, approvedStates.Remove());
1868 }
1869 else
1870 {
1871 string const nativeArch = _config->Find("APT::Architecture");
1872 unsigned long const oldSize = I->Pkg.end() == false ? Size : 0;
1873 for (;I != J && Size < MaxArgBytes; ++I)
1874 {
1875 if((*I).Pkg.end() == true)
1876 continue;
1877 if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.FullName(true)) != disappearedPkgs.end())
1878 continue;
1879 // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian
1880 if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch ||
1881 strcmp(I->Pkg.Arch(), "all") == 0 ||
1882 strcmp(I->Pkg.Arch(), "none") == 0))
1883 {
1884 char const * const name = I->Pkg.Name();
1885 ADDARG(name);
1886 }
1887 else
1888 {
1889 pkgCache::VerIterator PkgVer;
1890 std::string name = I->Pkg.Name();
1891 if (Op == Item::Remove)
1892 PkgVer = I->Pkg.CurrentVer();
1893 else if (Op == Item::Purge)
1894 {
1895 // we purge later with --purge --pending, so if it isn't installed (aka rc-only), skip it here
1896 PkgVer = I->Pkg.CurrentVer();
1897 if (PkgVer.end() == true)
1898 continue;
1899 }
1900 else
1901 PkgVer = Cache[I->Pkg].InstVerIter(Cache);
1902 if (strcmp(I->Pkg.Arch(), "none") == 0)
1903 ; // never arch-qualify a package without an arch
1904 else if (PkgVer.end() == false)
1905 name.append(":").append(PkgVer.Arch());
1906 else
1907 _error->Warning("Can not find PkgVer for '%s'", name.c_str());
1908 char * const fullname = strdup(name.c_str());
1909 Packages.push_back(fullname);
1910 ADDARG(fullname);
1911 }
1912 }
1913 // skip configure action if all sheduled packages disappeared
1914 if (oldSize == Size)
1915 continue;
1916 }
1917 #undef ADDARGC
1918 #undef ADDARG
1919
1920 J = I;
1921
1922 if (noopDPkgInvocation == true)
1923 {
1924 for (std::vector<const char *>::const_iterator a = Args.begin();
1925 a != Args.end(); ++a)
1926 clog << *a << ' ';
1927 clog << endl;
1928 for (std::vector<char *>::const_iterator p = Packages.begin();
1929 p != Packages.end(); ++p)
1930 free(*p);
1931 Packages.clear();
1932 close(fd[0]);
1933 close(fd[1]);
1934 cleanUpTmpDir(tmpdir_to_free);
1935 continue;
1936 }
1937 Args.push_back(NULL);
1938
1939 cout << flush;
1940 clog << flush;
1941 cerr << flush;
1942
1943 /* Mask off sig int/quit. We do this because dpkg also does when
1944 it forks scripts. What happens is that when you hit ctrl-c it sends
1945 it to all processes in the group. Since dpkg ignores the signal
1946 it doesn't die but we do! So we must also ignore it */
1947 sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
1948 sighandler_t old_SIGINT = signal(SIGINT,SigINT);
1949
1950 // Check here for any SIGINT
1951 if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install))
1952 break;
1953
1954 // ignore SIGHUP as well (debian #463030)
1955 sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN);
1956
1957 // now run dpkg
1958 d->progress->StartDpkg();
1959 std::set<int> KeepFDs;
1960 KeepFDs.insert(fd[1]);
1961 MergeKeepFdsFromConfiguration(KeepFDs);
1962 pid_t Child = ExecFork(KeepFDs);
1963 if (Child == 0)
1964 {
1965 // This is the child
1966 SetupSlavePtyMagic();
1967 close(fd[0]); // close the read end of the pipe
1968
1969 debSystem::DpkgChrootDirectory();
1970
1971 if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
1972 _exit(100);
1973
1974 if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
1975 {
1976 int Flags;
1977 int dummy = 0;
1978 if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
1979 _exit(100);
1980
1981 // Discard everything in stdin before forking dpkg
1982 if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
1983 _exit(100);
1984
1985 while (read(STDIN_FILENO,&dummy,1) == 1);
1986
1987 if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
1988 _exit(100);
1989 }
1990
1991 // if color support isn't enabled/disabled explicitly tell
1992 // dpkg to use the same state apt is using for its color support
1993 if (_config->FindB("APT::Color", false) == true)
1994 setenv("DPKG_COLORS", "always", 0);
1995 else
1996 setenv("DPKG_COLORS", "never", 0);
1997
1998 execvp(Args[0], (char**) &Args[0]);
1999 cerr << "Could not exec dpkg!" << endl;
2000 _exit(100);
2001 }
2002
2003 // we read from dpkg here
2004 int const _dpkgin = fd[0];
2005 close(fd[1]); // close the write end of the pipe
2006
2007 // apply ionice
2008 if (_config->FindB("DPkg::UseIoNice", false) == true)
2009 ionice(Child);
2010
2011 // setups fds
2012 sigemptyset(&d->sigmask);
2013 sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask);
2014
2015 /* free vectors (and therefore memory) as we don't need the included data anymore */
2016 for (std::vector<char *>::const_iterator p = Packages.begin();
2017 p != Packages.end(); ++p)
2018 free(*p);
2019 Packages.clear();
2020
2021 // the result of the waitpid call
2022 int Status = 0;
2023 int res;
2024 bool waitpid_failure = false;
2025 while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
2026 if(res < 0) {
2027 // error handling, waitpid returned -1
2028 if (errno == EINTR)
2029 continue;
2030 waitpid_failure = true;
2031 break;
2032 }
2033
2034 // wait for input or output here
2035 FD_ZERO(&rfds);
2036 if (d->master >= 0 && d->direct_stdin == false && d->stdin_is_dev_null == false)
2037 FD_SET(STDIN_FILENO, &rfds);
2038 FD_SET(_dpkgin, &rfds);
2039 if(d->master >= 0)
2040 FD_SET(d->master, &rfds);
2041 tv.tv_sec = 0;
2042 tv.tv_nsec = d->progress->GetPulseInterval();
2043 auto const select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL,
2044 &tv, &d->original_sigmask);
2045 d->progress->Pulse();
2046 if (select_ret == 0)
2047 continue;
2048 else if (select_ret < 0 && errno == EINTR)
2049 continue;
2050 else if (select_ret < 0)
2051 {
2052 perror("select() returned error");
2053 continue;
2054 }
2055
2056 if(d->master >= 0 && FD_ISSET(d->master, &rfds))
2057 DoTerminalPty(d->master);
2058 if(d->master >= 0 && FD_ISSET(0, &rfds))
2059 DoStdin(d->master);
2060 if(FD_ISSET(_dpkgin, &rfds))
2061 DoDpkgStatusFd(_dpkgin);
2062 }
2063 close(_dpkgin);
2064
2065 // Restore sig int/quit
2066 signal(SIGQUIT,old_SIGQUIT);
2067 signal(SIGINT,old_SIGINT);
2068 signal(SIGHUP,old_SIGHUP);
2069
2070 cleanUpTmpDir(tmpdir_to_free);
2071
2072 if (waitpid_failure == true)
2073 {
2074 strprintf(d->dpkg_error, "Sub-process %s couldn't be waited for.",Args[0]);
2075 _error->Error("%s", d->dpkg_error.c_str());
2076 break;
2077 }
2078
2079 // Check for an error code.
2080 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
2081 {
2082 // if it was set to "keep-dpkg-running" then we won't return
2083 // here but keep the loop going and just report it as a error
2084 // for later
2085 bool const stopOnError = _config->FindB("Dpkg::StopOnError",true);
2086
2087 if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
2088 strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]);
2089 else if (WIFEXITED(Status) != 0)
2090 strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status));
2091 else
2092 strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]);
2093 _error->Error("%s", d->dpkg_error.c_str());
2094
2095 if(stopOnError)
2096 break;
2097 }
2098 }
2099 // dpkg is done at this point
2100 StopPtyMagic();
2101 CloseLog();
2102
2103 if (d->dpkg_error.empty() == false)
2104 {
2105 // no point in reseting packages we already completed removal for
2106 StripAlreadyDoneFrom(approvedStates.Remove());
2107 StripAlreadyDoneFrom(approvedStates.Purge());
2108 APT::StateChanges undo;
2109 auto && undoRem = approvedStates.Remove();
2110 std::move(undoRem.begin(), undoRem.end(), std::back_inserter(undo.Install()));
2111 auto && undoPur = approvedStates.Purge();
2112 std::move(undoPur.begin(), undoPur.end(), std::back_inserter(undo.Install()));
2113 approvedStates.clear();
2114 if (undo.Save(false) == false)
2115 _error->Error("Couldn't revert dpkg selection for approved remove/purge after an error was encountered!");
2116 }
2117
2118 StripAlreadyDoneFrom(currentStates.Remove());
2119 StripAlreadyDoneFrom(currentStates.Purge());
2120 if (currentStates.Save(false) == false)
2121 _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
2122
2123 if (pkgPackageManager::SigINTStop)
2124 _error->Warning(_("Operation was interrupted before it could finish"));
2125
2126 if (noopDPkgInvocation == false)
2127 {
2128 if (d->dpkg_error.empty() && (PackagesDone + 1) != PackagesTotal)
2129 {
2130 std::string pkglist;
2131 for (auto const &PO: PackageOps)
2132 if (PO.second.size() != PackageOpsDone[PO.first])
2133 {
2134 if (pkglist.empty() == false)
2135 pkglist.append(" ");
2136 pkglist.append(PO.first);
2137 }
2138 /* who cares about correct progress? As we depend on it for skipping actions
2139 our parsing should be correct. People will no doubt be confused if they see
2140 this message, but the dpkg warning about unknown packages isn't much better
2141 from a user POV and combined we might have a chance to figure out what is wrong */
2142 _error->Warning("APT had planned for dpkg to do more than it reported back (%u vs %u).\n"
2143 "Affected packages: %s", PackagesDone, PackagesTotal, pkglist.c_str());
2144 }
2145
2146 std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache");
2147 if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true &&
2148 RemoveFile("pkgDPkgPM::Go", oldpkgcache))
2149 {
2150 std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache");
2151 if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true)
2152 {
2153 _error->PushToStack();
2154 pkgCacheFile CacheFile;
2155 CacheFile.BuildCaches(NULL, true);
2156 _error->RevertToStack();
2157 }
2158 }
2159 }
2160
2161 // disappearing packages can forward their auto-bit
2162 if (disappearedPkgs.empty() == false)
2163 Cache.writeStateFile(NULL);
2164
2165 d->progress->Stop();
2166
2167 if (RunScripts("DPkg::Post-Invoke") == false)
2168 return false;
2169
2170 return d->dpkg_error.empty();
2171 }
2172
2173 void SigINT(int /*sig*/) {
2174 pkgPackageManager::SigINTStop = true;
2175 }
2176 /*}}}*/
2177 // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/
2178 // ---------------------------------------------------------------------
2179 /* */
2180 void pkgDPkgPM::Reset()
2181 {
2182 List.erase(List.begin(),List.end());
2183 }
2184 /*}}}*/
2185 // pkgDpkgPM::WriteApportReport - write out error report pkg failure /*{{{*/
2186 // ---------------------------------------------------------------------
2187 /* */
2188 void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
2189 {
2190 // If apport doesn't exist or isn't installed do nothing
2191 // This e.g. prevents messages in 'universes' without apport
2192 pkgCache::PkgIterator apportPkg = Cache.FindPkg("apport");
2193 if (apportPkg.end() == true || apportPkg->CurrentVer == 0)
2194 return;
2195
2196 string pkgname, reportfile, pkgver, arch;
2197 string::size_type pos;
2198 FILE *report;
2199
2200 if (_config->FindB("Dpkg::ApportFailureReport", true) == false)
2201 {
2202 std::clog << "configured to not write apport reports" << std::endl;
2203 return;
2204 }
2205
2206 // only report the first errors
2207 if(pkgFailures > _config->FindI("APT::Apport::MaxReports", 3))
2208 {
2209 std::clog << _("No apport report written because MaxReports is reached already") << std::endl;
2210 return;
2211 }
2212
2213 // check if its not a follow up error
2214 const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured");
2215 if(strstr(errormsg, needle) != NULL) {
2216 std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl;
2217 return;
2218 }
2219
2220 // do not report disk-full failures
2221 if(strstr(errormsg, strerror(ENOSPC)) != NULL) {
2222 std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl;
2223 return;
2224 }
2225
2226 // do not report out-of-memory failures
2227 if(strstr(errormsg, strerror(ENOMEM)) != NULL ||
2228 strstr(errormsg, "failed to allocate memory") != NULL) {
2229 std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl;
2230 return;
2231 }
2232
2233 // do not report bugs regarding inaccessible local files
2234 if(strstr(errormsg, strerror(ENOENT)) != NULL ||
2235 strstr(errormsg, "cannot access archive") != NULL) {
2236 std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
2237 return;
2238 }
2239
2240 // do not report errors encountered when decompressing packages
2241 if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) {
2242 std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
2243 return;
2244 }
2245
2246 // do not report dpkg I/O errors, this is a format string, so we compare
2247 // the prefix and the suffix of the error with the dpkg error message
2248 vector<string> io_errors;
2249 io_errors.push_back(string("failed to read"));
2250 io_errors.push_back(string("failed to write"));
2251 io_errors.push_back(string("failed to seek"));
2252 io_errors.push_back(string("unexpected end of file or stream"));
2253
2254 for (vector<string>::iterator I = io_errors.begin(); I != io_errors.end(); ++I)
2255 {
2256 vector<string> list = VectorizeString(dgettext("dpkg", (*I).c_str()), '%');
2257 if (list.size() > 1) {
2258 // we need to split %s, VectorizeString only allows char so we need
2259 // to kill the "s" manually
2260 if (list[1].size() > 1) {
2261 list[1].erase(0, 1);
2262 if(strstr(errormsg, list[0].c_str()) &&
2263 strstr(errormsg, list[1].c_str())) {
2264 std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl;
2265 return;
2266 }
2267 }
2268 }
2269 }
2270
2271 // get the pkgname and reportfile
2272 pkgname = flNotDir(pkgpath);
2273 pos = pkgname.find('_');
2274 if(pos != string::npos)
2275 pkgname = pkgname.substr(0, pos);
2276
2277 // find the package version and source package name
2278 pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
2279 if (Pkg.end() == true)
2280 {
2281 if (pos == std::string::npos || _config->FindB("dpkg::install::recursive::numbered", true) == false)
2282 return;
2283 auto const dash = pkgname.find_first_not_of("0123456789");
2284 if (dash == std::string::npos || pkgname[dash] != '-')
2285 return;
2286 pkgname.erase(0, dash + 1);
2287 Pkg = Cache.FindPkg(pkgname);
2288 if (Pkg.end() == true)
2289 return;
2290 }
2291 pkgCache::VerIterator Ver = Cache.GetCandidateVersion(Pkg);
2292 if (Ver.end() == true)
2293 return;
2294 pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr();
2295
2296 // if the file exists already, we check:
2297 // - if it was reported already (touched by apport).
2298 // If not, we do nothing, otherwise
2299 // we overwrite it. This is the same behaviour as apport
2300 // - if we have a report with the same pkgversion already
2301 // then we skip it
2302 _config->CndSet("Dir::Apport", "var/crash");
2303 reportfile = flCombine(_config->FindDir("Dir::Apport", "var/crash"), pkgname+".0.crash");
2304 if(FileExists(reportfile))
2305 {
2306 struct stat buf;
2307 char strbuf[255];
2308
2309 // check atime/mtime
2310 stat(reportfile.c_str(), &buf);
2311 if(buf.st_mtime > buf.st_atime)
2312 return;
2313
2314 // check if the existing report is the same version
2315 report = fopen(reportfile.c_str(),"r");
2316 while(fgets(strbuf, sizeof(strbuf), report) != NULL)
2317 {
2318 if(strstr(strbuf,"Package:") == strbuf)
2319 {
2320 char pkgname[255], version[255];
2321 if(sscanf(strbuf, "Package: %254s %254s", pkgname, version) == 2)
2322 if(strcmp(pkgver.c_str(), version) == 0)
2323 {
2324 fclose(report);
2325 return;
2326 }
2327 }
2328 }
2329 fclose(report);
2330 }
2331
2332 // now write the report
2333 arch = _config->Find("APT::Architecture");
2334 report = fopen(reportfile.c_str(),"w");
2335 if(report == NULL)
2336 return;
2337 if(_config->FindB("DPkgPM::InitialReportOnly",false) == true)
2338 chmod(reportfile.c_str(), 0);
2339 else
2340 chmod(reportfile.c_str(), 0600);
2341 fprintf(report, "ProblemType: Package\n");
2342 fprintf(report, "Architecture: %s\n", arch.c_str());
2343 time_t now = time(NULL);
2344 char ctime_buf[26]; // need at least 26 bytes according to ctime(3)
2345 fprintf(report, "Date: %s" , ctime_r(&now, ctime_buf));
2346 fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str());
2347 fprintf(report, "SourcePackage: %s\n", Ver.SourcePkgName());
2348 fprintf(report, "ErrorMessage:\n %s\n", errormsg);
2349
2350 // ensure that the log is flushed
2351 if(d->term_out)
2352 fflush(d->term_out);
2353
2354 // attach terminal log it if we have it
2355 string logfile_name = _config->FindFile("Dir::Log::Terminal");
2356 if (!logfile_name.empty())
2357 {
2358 FILE *log = NULL;
2359
2360 fprintf(report, "DpkgTerminalLog:\n");
2361 log = fopen(logfile_name.c_str(),"r");
2362 if(log != NULL)
2363 {
2364 char buf[1024];
2365 while( fgets(buf, sizeof(buf), log) != NULL)
2366 fprintf(report, " %s", buf);
2367 fprintf(report, " \n");
2368 fclose(log);
2369 }
2370 }
2371
2372 // attach history log it if we have it
2373 string histfile_name = _config->FindFile("Dir::Log::History");
2374 if (!histfile_name.empty())
2375 {
2376 fprintf(report, "DpkgHistoryLog:\n");
2377 FILE* log = fopen(histfile_name.c_str(),"r");
2378 if(log != NULL)
2379 {
2380 char buf[1024];
2381 while( fgets(buf, sizeof(buf), log) != NULL)
2382 fprintf(report, " %s", buf);
2383 fclose(log);
2384 }
2385 }
2386
2387 // log the ordering, see dpkgpm.h and the "Ops" enum there
2388 fprintf(report, "AptOrdering:\n");
2389 for (auto && I : List)
2390 {
2391 char const * opstr = nullptr;
2392 switch (I.Op)
2393 {
2394 case Item::Install: opstr = "Install"; break;
2395 case Item::Configure: opstr = "Configure"; break;
2396 case Item::Remove: opstr = "Remove"; break;
2397 case Item::Purge: opstr = "Purge"; break;
2398 case Item::ConfigurePending: opstr = "ConfigurePending"; break;
2399 case Item::TriggersPending: opstr = "TriggersPending"; break;
2400 case Item::RemovePending: opstr = "RemovePending"; break;
2401 case Item::PurgePending: opstr = "PurgePending"; break;
2402 }
2403 auto const pkgname = I.Pkg.end() ? "NULL" : I.Pkg.FullName();
2404 fprintf(report, " %s: %s\n", pkgname.c_str(), opstr);
2405 }
2406
2407 // attach dmesg log (to learn about segfaults)
2408 if (FileExists("/bin/dmesg"))
2409 {
2410 fprintf(report, "Dmesg:\n");
2411 FILE *log = popen("/bin/dmesg","r");
2412 if(log != NULL)
2413 {
2414 char buf[1024];
2415 while( fgets(buf, sizeof(buf), log) != NULL)
2416 fprintf(report, " %s", buf);
2417 pclose(log);
2418 }
2419 }
2420
2421 // attach df -l log (to learn about filesystem status)
2422 if (FileExists("/bin/df"))
2423 {
2424
2425 fprintf(report, "Df:\n");
2426 FILE *log = popen("/bin/df -l","r");
2427 if(log != NULL)
2428 {
2429 char buf[1024];
2430 while( fgets(buf, sizeof(buf), log) != NULL)
2431 fprintf(report, " %s", buf);
2432 pclose(log);
2433 }
2434 }
2435
2436 fclose(report);
2437
2438 }
2439 /*}}}*/