]> git.saurik.com Git - apt.git/blob - apt-pkg/deb/dpkgpm.cc
gets file location via FindFile instead of manual merge
[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 logfile_name = _config->FindFile("Dir::Log::Terminal");
989 string logdir = flNotFile(logfile_name);
990 if(CreateAPTDirectoryIfNeeded(logdir, logdir) == false)
991 // FIXME: use a better string after freeze
992 return _error->Error(_("Directory '%s' missing"), logdir.c_str());
993
994 // get current time
995 char timestr[200];
996 time_t const t = time(NULL);
997 struct tm tm_buf;
998 struct tm const * const tmp = localtime_r(&t, &tm_buf);
999 strftime(timestr, sizeof(timestr), "%F %T", tmp);
1000
1001 // open terminal log
1002 if (!logfile_name.empty())
1003 {
1004 d->term_out = fopen(logfile_name.c_str(),"a");
1005 if (d->term_out == NULL)
1006 return _error->WarningE("OpenLog", _("Could not open file '%s'"), logfile_name.c_str());
1007 setvbuf(d->term_out, NULL, _IONBF, 0);
1008 SetCloseExec(fileno(d->term_out), true);
1009 if (getuid() == 0) // if we aren't root, we can't chown a file, so don't try it
1010 {
1011 struct passwd *pw = getpwnam("root");
1012 struct group *gr = getgrnam("adm");
1013 if (pw != NULL && gr != NULL && chown(logfile_name.c_str(), pw->pw_uid, gr->gr_gid) != 0)
1014 _error->WarningE("OpenLog", "chown to root:adm of file %s failed", logfile_name.c_str());
1015 }
1016 if (chmod(logfile_name.c_str(), 0640) != 0)
1017 _error->WarningE("OpenLog", "chmod 0640 of file %s failed", logfile_name.c_str());
1018 fprintf(d->term_out, "\nLog started: %s\n", timestr);
1019 }
1020
1021 // write your history
1022 string const history_name = _config->FindFile("Dir::Log::History");
1023 string logdir2 = flNotFile(logfile_name);
1024 if(logdir != logdir2 && CreateAPTDirectoryIfNeeded(logdir2, logdir2) == false)
1025 return _error->Error(_("Directory '%s' missing"), logdir.c_str());
1026 if (!history_name.empty())
1027 {
1028 d->history_out = fopen(history_name.c_str(),"a");
1029 if (d->history_out == NULL)
1030 return _error->WarningE("OpenLog", _("Could not open file '%s'"), history_name.c_str());
1031 SetCloseExec(fileno(d->history_out), true);
1032 chmod(history_name.c_str(), 0644);
1033 fprintf(d->history_out, "\nStart-Date: %s\n", timestr);
1034 string remove, purge, install, reinstall, upgrade, downgrade;
1035 for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I)
1036 {
1037 enum { CANDIDATE, CANDIDATE_AUTO, CURRENT_CANDIDATE, CURRENT } infostring;
1038 string *line = NULL;
1039 #define HISTORYINFO(X, Y) { line = &X; infostring = Y; }
1040 if (Cache[I].NewInstall() == true)
1041 HISTORYINFO(install, CANDIDATE_AUTO)
1042 else if (Cache[I].ReInstall() == true)
1043 HISTORYINFO(reinstall, CANDIDATE)
1044 else if (Cache[I].Upgrade() == true)
1045 HISTORYINFO(upgrade, CURRENT_CANDIDATE)
1046 else if (Cache[I].Downgrade() == true)
1047 HISTORYINFO(downgrade, CURRENT_CANDIDATE)
1048 else if (Cache[I].Delete() == true)
1049 HISTORYINFO((Cache[I].Purge() ? purge : remove), CURRENT)
1050 else
1051 continue;
1052 #undef HISTORYINFO
1053 line->append(I.FullName(false)).append(" (");
1054 switch (infostring) {
1055 case CANDIDATE: line->append(Cache[I].CandVersion); break;
1056 case CANDIDATE_AUTO:
1057 line->append(Cache[I].CandVersion);
1058 if ((Cache[I].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
1059 line->append(", automatic");
1060 break;
1061 case CURRENT_CANDIDATE: line->append(Cache[I].CurVersion).append(", ").append(Cache[I].CandVersion); break;
1062 case CURRENT: line->append(Cache[I].CurVersion); break;
1063 }
1064 line->append("), ");
1065 }
1066 if (_config->Exists("Commandline::AsString") == true)
1067 WriteHistoryTag("Commandline", _config->Find("Commandline::AsString"));
1068 std::string RequestingUser = AptHistoryRequestingUser();
1069 if (RequestingUser != "")
1070 WriteHistoryTag("Requested-By", RequestingUser);
1071 WriteHistoryTag("Install", install);
1072 WriteHistoryTag("Reinstall", reinstall);
1073 WriteHistoryTag("Upgrade", upgrade);
1074 WriteHistoryTag("Downgrade",downgrade);
1075 WriteHistoryTag("Remove",remove);
1076 WriteHistoryTag("Purge",purge);
1077 fflush(d->history_out);
1078 }
1079
1080 return true;
1081 }
1082 /*}}}*/
1083 // DPkg::CloseLog /*{{{*/
1084 bool pkgDPkgPM::CloseLog()
1085 {
1086 char timestr[200];
1087 time_t t = time(NULL);
1088 struct tm tm_buf;
1089 struct tm *tmp = localtime_r(&t, &tm_buf);
1090 strftime(timestr, sizeof(timestr), "%F %T", tmp);
1091
1092 if(d->term_out)
1093 {
1094 fprintf(d->term_out, "Log ended: ");
1095 fprintf(d->term_out, "%s", timestr);
1096 fprintf(d->term_out, "\n");
1097 fclose(d->term_out);
1098 }
1099 d->term_out = NULL;
1100
1101 if(d->history_out)
1102 {
1103 if (disappearedPkgs.empty() == false)
1104 {
1105 string disappear;
1106 for (std::set<std::string>::const_iterator d = disappearedPkgs.begin();
1107 d != disappearedPkgs.end(); ++d)
1108 {
1109 pkgCache::PkgIterator P = Cache.FindPkg(*d);
1110 disappear.append(*d);
1111 if (P.end() == true)
1112 disappear.append(", ");
1113 else
1114 disappear.append(" (").append(Cache[P].CurVersion).append("), ");
1115 }
1116 WriteHistoryTag("Disappeared", disappear);
1117 }
1118 if (d->dpkg_error.empty() == false)
1119 fprintf(d->history_out, "Error: %s\n", d->dpkg_error.c_str());
1120 fprintf(d->history_out, "End-Date: %s\n", timestr);
1121 fclose(d->history_out);
1122 }
1123 d->history_out = NULL;
1124
1125 return true;
1126 }
1127 /*}}}*/
1128
1129 // DPkgPM::BuildPackagesProgressMap /*{{{*/
1130 void pkgDPkgPM::BuildPackagesProgressMap()
1131 {
1132 // map the dpkg states to the operations that are performed
1133 // (this is sorted in the same way as Item::Ops)
1134 static const std::array<std::array<DpkgState, 3>, 4> DpkgStatesOpMap = {{
1135 // Install operation
1136 {{
1137 {"half-installed", N_("Preparing %s")},
1138 {"unpacked", N_("Unpacking %s") },
1139 {nullptr, nullptr}
1140 }},
1141 // Configure operation
1142 {{
1143 {"unpacked",N_("Preparing to configure %s") },
1144 {"half-configured", N_("Configuring %s") },
1145 { "installed", N_("Installed %s")},
1146 }},
1147 // Remove operation
1148 {{
1149 {"half-configured", N_("Preparing for removal of %s")},
1150 {"half-installed", N_("Removing %s")},
1151 {"config-files", N_("Removed %s")},
1152 }},
1153 // Purge operation
1154 {{
1155 {"config-files", N_("Preparing to completely remove %s")},
1156 {"not-installed", N_("Completely removed %s")},
1157 {nullptr, nullptr}
1158 }},
1159 }};
1160 static_assert(Item::Purge == 3, "Enum item has unexpected index for mapping array");
1161
1162 // init the PackageOps map, go over the list of packages that
1163 // that will be [installed|configured|removed|purged] and add
1164 // them to the PackageOps map (the dpkg states it goes through)
1165 // and the PackageOpsTranslations (human readable strings)
1166 for (auto &&I : List)
1167 {
1168 if(I.Pkg.end() == true)
1169 continue;
1170
1171 string const name = I.Pkg.FullName();
1172 PackageOpsDone[name] = 0;
1173 auto AddToPackageOps = std::back_inserter(PackageOps[name]);
1174 if (I.Op == Item::Purge && I.Pkg->CurrentVer != 0)
1175 {
1176 // purging a package which is installed first passes through remove states
1177 auto const DpkgOps = DpkgStatesOpMap[Item::Remove];
1178 std::copy(DpkgOps.begin(), DpkgOps.end(), AddToPackageOps);
1179 PackagesTotal += DpkgOps.size();
1180 }
1181 auto const DpkgOps = DpkgStatesOpMap[I.Op];
1182 std::copy_if(DpkgOps.begin(), DpkgOps.end(), AddToPackageOps, [&](DpkgState const &state) {
1183 if (state.state == nullptr)
1184 return false;
1185 ++PackagesTotal;
1186 return true;
1187 });
1188 if ((I.Op == Item::Remove || I.Op == Item::Purge) && I.Pkg->CurrentVer != 0)
1189 {
1190 if (I.Pkg->CurrentState == pkgCache::State::UnPacked ||
1191 I.Pkg->CurrentState == pkgCache::State::HalfInstalled)
1192 {
1193 if (likely(strcmp(PackageOps[name][0].state, "half-configured") == 0))
1194 {
1195 ++PackageOpsDone[name];
1196 --PackagesTotal;
1197 }
1198 }
1199 }
1200 }
1201 /* one extra: We don't want the progress bar to reach 100%, especially not
1202 if we call dpkg --configure --pending and process a bunch of triggers
1203 while showing 100%. Also, spindown takes a while, so never reaching 100%
1204 is way more correct than reaching 100% while still doing stuff even if
1205 doing it this way is slightly bending the rules */
1206 ++PackagesTotal;
1207 }
1208 /*}}}*/
1209 bool pkgDPkgPM::Go(int StatusFd) /*{{{*/
1210 {
1211 APT::Progress::PackageManager *progress = NULL;
1212 if (StatusFd == -1)
1213 progress = APT::Progress::PackageManagerProgressFactory();
1214 else
1215 progress = new APT::Progress::PackageManagerProgressFd(StatusFd);
1216
1217 return Go(progress);
1218 }
1219 /*}}}*/
1220 void pkgDPkgPM::StartPtyMagic() /*{{{*/
1221 {
1222 if (_config->FindB("Dpkg::Use-Pty", true) == false)
1223 {
1224 d->master = -1;
1225 if (d->slave != NULL)
1226 free(d->slave);
1227 d->slave = NULL;
1228 return;
1229 }
1230
1231 if (isatty(STDIN_FILENO) == 0)
1232 d->direct_stdin = true;
1233
1234 _error->PushToStack();
1235
1236 d->master = posix_openpt(O_RDWR | O_NOCTTY);
1237 if (d->master == -1)
1238 _error->Errno("posix_openpt", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
1239 else if (unlockpt(d->master) == -1)
1240 _error->Errno("unlockpt", "Unlocking the slave of master fd %d failed!", d->master);
1241 else
1242 {
1243 #ifdef HAVE_PTSNAME_R
1244 char slave_name[64]; // 64 is used by bionic
1245 if (ptsname_r(d->master, slave_name, sizeof(slave_name)) != 0)
1246 #else
1247 char const * const slave_name = ptsname(d->master);
1248 if (slave_name == NULL)
1249 #endif
1250 _error->Errno("ptsname", "Getting name for slave of master fd %d failed!", d->master);
1251 else
1252 {
1253 d->slave = strdup(slave_name);
1254 if (d->slave == NULL)
1255 _error->Errno("strdup", "Copying name %s for slave of master fd %d failed!", slave_name, d->master);
1256 else if (grantpt(d->master) == -1)
1257 _error->Errno("grantpt", "Granting access to slave %s based on master fd %d failed!", slave_name, d->master);
1258 else if (tcgetattr(STDIN_FILENO, &d->tt) == 0)
1259 {
1260 d->tt_is_valid = true;
1261 struct termios raw_tt;
1262 // copy window size of stdout if its a 'good' terminal
1263 if (tcgetattr(STDOUT_FILENO, &raw_tt) == 0)
1264 {
1265 struct winsize win;
1266 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0)
1267 _error->Errno("ioctl", "Getting TIOCGWINSZ from stdout failed!");
1268 if (ioctl(d->master, TIOCSWINSZ, &win) < 0)
1269 _error->Errno("ioctl", "Setting TIOCSWINSZ for master fd %d failed!", d->master);
1270 }
1271 if (tcsetattr(d->master, TCSANOW, &d->tt) == -1)
1272 _error->Errno("tcsetattr", "Setting in Start via TCSANOW for master fd %d failed!", d->master);
1273
1274 raw_tt = d->tt;
1275 cfmakeraw(&raw_tt);
1276 raw_tt.c_lflag &= ~ECHO;
1277 raw_tt.c_lflag |= ISIG;
1278 // block SIGTTOU during tcsetattr to prevent a hang if
1279 // the process is a member of the background process group
1280 // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html
1281 sigemptyset(&d->sigmask);
1282 sigaddset(&d->sigmask, SIGTTOU);
1283 sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask);
1284 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_tt) == -1)
1285 _error->Errno("tcsetattr", "Setting in Start via TCSAFLUSH for stdin failed!");
1286 sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL);
1287
1288 }
1289 if (d->slave != NULL)
1290 {
1291 /* on linux, closing (and later reopening) all references to the slave
1292 makes the slave a death end, so we open it here to have one open all
1293 the time. We could use this fd in SetupSlavePtyMagic() for linux, but
1294 on kfreebsd we get an incorrect ("step like") output then while it has
1295 no problem with closing all references… so to avoid platform specific
1296 code here we combine both and be happy once more */
1297 d->protect_slave_from_dying = open(d->slave, O_RDWR | O_CLOEXEC | O_NOCTTY);
1298 }
1299 }
1300 }
1301
1302 if (_error->PendingError() == true)
1303 {
1304 if (d->master != -1)
1305 {
1306 close(d->master);
1307 d->master = -1;
1308 }
1309 if (d->slave != NULL)
1310 {
1311 free(d->slave);
1312 d->slave = NULL;
1313 }
1314 _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
1315 }
1316 _error->RevertToStack();
1317 }
1318 /*}}}*/
1319 void pkgDPkgPM::SetupSlavePtyMagic() /*{{{*/
1320 {
1321 if(d->master == -1 || d->slave == NULL)
1322 return;
1323
1324 if (close(d->master) == -1)
1325 _error->FatalE("close", "Closing master %d in child failed!", d->master);
1326 d->master = -1;
1327 if (setsid() == -1)
1328 _error->FatalE("setsid", "Starting a new session for child failed!");
1329
1330 int const slaveFd = open(d->slave, O_RDWR | O_NOCTTY);
1331 if (slaveFd == -1)
1332 _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
1333 else if (ioctl(slaveFd, TIOCSCTTY, 0) < 0)
1334 _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd);
1335 else
1336 {
1337 unsigned short i = 0;
1338 if (d->direct_stdin == true)
1339 ++i;
1340 for (; i < 3; ++i)
1341 if (dup2(slaveFd, i) == -1)
1342 _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i);
1343
1344 if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSANOW, &d->tt) < 0)
1345 _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd);
1346 }
1347
1348 if (slaveFd != -1)
1349 close(slaveFd);
1350 }
1351 /*}}}*/
1352 void pkgDPkgPM::StopPtyMagic() /*{{{*/
1353 {
1354 if (d->slave != NULL)
1355 free(d->slave);
1356 d->slave = NULL;
1357 if (d->protect_slave_from_dying != -1)
1358 {
1359 close(d->protect_slave_from_dying);
1360 d->protect_slave_from_dying = -1;
1361 }
1362 if(d->master >= 0)
1363 {
1364 if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSAFLUSH, &d->tt) == -1)
1365 _error->FatalE("tcsetattr", "Setting in Stop via TCSAFLUSH for stdin failed!");
1366 close(d->master);
1367 d->master = -1;
1368 }
1369 }
1370 /*}}}*/
1371 static void cleanUpTmpDir(char * const tmpdir) /*{{{*/
1372 {
1373 if (tmpdir == nullptr)
1374 return;
1375 DIR * const D = opendir(tmpdir);
1376 if (D == nullptr)
1377 _error->Errno("opendir", _("Unable to read %s"), tmpdir);
1378 else
1379 {
1380 auto const dfd = dirfd(D);
1381 for (struct dirent *Ent = readdir(D); Ent != nullptr; Ent = readdir(D))
1382 {
1383 if (Ent->d_name[0] == '.')
1384 continue;
1385 #ifdef _DIRENT_HAVE_D_TYPE
1386 if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN))
1387 continue;
1388 #endif
1389 if (unlikely(unlinkat(dfd, Ent->d_name, 0) != 0))
1390 break;
1391 }
1392 closedir(D);
1393 rmdir(tmpdir);
1394 }
1395 free(tmpdir);
1396 }
1397 /*}}}*/
1398
1399 // DPkgPM::Go - Run the sequence /*{{{*/
1400 // ---------------------------------------------------------------------
1401 /* This globs the operations and calls dpkg
1402 *
1403 * If it is called with a progress object apt will report the install
1404 * progress to this object. It maps the dpkg states a package goes
1405 * through to human readable (and i10n-able)
1406 * names and calculates a percentage for each step.
1407 */
1408 static bool ItemIsEssential(pkgDPkgPM::Item const &I)
1409 {
1410 static auto const cachegen = _config->Find("pkgCacheGen::Essential");
1411 if (cachegen == "none" || cachegen == "native")
1412 return true;
1413 if (unlikely(I.Pkg.end()))
1414 return true;
1415 return (I.Pkg->Flags & pkgCache::Flag::Essential) != 0;
1416 }
1417 bool pkgDPkgPM::ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache)
1418 {
1419 {
1420 std::unordered_set<decltype(pkgCache::Package::ID)> alreadyRemoved;
1421 for (auto && I : List)
1422 if (I.Op == Item::Remove || I.Op == Item::Purge)
1423 alreadyRemoved.insert(I.Pkg->ID);
1424 std::remove_reference<decltype(List)>::type AppendList;
1425 for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
1426 if (Cache[Pkg].Delete() && alreadyRemoved.insert(Pkg->ID).second == true)
1427 AppendList.emplace_back(Cache[Pkg].Purge() ? Item::Purge : Item::Remove, Pkg);
1428 std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
1429 }
1430 {
1431 std::unordered_set<decltype(pkgCache::Package::ID)> alreadyConfigured;
1432 for (auto && I : List)
1433 if (I.Op == Item::Configure)
1434 alreadyConfigured.insert(I.Pkg->ID);
1435 std::remove_reference<decltype(List)>::type AppendList;
1436 for (auto && I : List)
1437 if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true)
1438 AppendList.emplace_back(Item::Configure, I.Pkg);
1439 for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
1440 if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure &&
1441 Cache[Pkg].Delete() == false && alreadyConfigured.insert(Pkg->ID).second == true)
1442 AppendList.emplace_back(Item::Configure, Pkg);
1443 std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
1444 }
1445 return true;
1446 }
1447 bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
1448 {
1449 // explicitely remove&configure everything for hookscripts and progress building
1450 // we need them only temporarily through, so keep the length and erase afterwards
1451 decltype(List)::const_iterator::difference_type explicitIdx =
1452 std::distance(List.cbegin(), List.cend());
1453 ExpandPendingCalls(List, Cache);
1454
1455 /* if dpkg told us that it has already done everything to the package we wanted it to do,
1456 we shouldn't ask it for "more" later. That can e.g. happen if packages without conffiles
1457 are purged as they will have pass through the purge states on remove already */
1458 auto const StripAlreadyDoneFrom = [&](APT::VersionVector & Pending) {
1459 Pending.erase(std::remove_if(Pending.begin(), Pending.end(), [&](pkgCache::VerIterator const &Ver) {
1460 auto const PN = Ver.ParentPkg().FullName();
1461 auto const POD = PackageOpsDone.find(PN);
1462 if (POD == PackageOpsDone.end())
1463 return false;
1464 return PackageOps[PN].size() <= POD->second;
1465 }), Pending.end());
1466 };
1467
1468 pkgPackageManager::SigINTStop = false;
1469 d->progress = progress;
1470
1471 // Generate the base argument list for dpkg
1472 std::vector<std::string> const sArgs = debSystem::GetDpkgBaseCommand();
1473 std::vector<const char *> Args(sArgs.size(), NULL);
1474 std::transform(sArgs.begin(), sArgs.end(), Args.begin(),
1475 [](std::string const &s) { return s.c_str(); });
1476 unsigned long long const StartSize = std::accumulate(sArgs.begin(), sArgs.end(), 0llu,
1477 [](unsigned long long const i, std::string const &s) { return i + s.length(); });
1478 size_t const BaseArgs = Args.size();
1479
1480 fd_set rfds;
1481 struct timespec tv;
1482
1483 // try to figure out the max environment size
1484 int OSArgMax = sysconf(_SC_ARG_MAX);
1485 if(OSArgMax < 0)
1486 OSArgMax = 32*1024;
1487 OSArgMax -= EnvironmentSize() - 2*1024;
1488 unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax);
1489 bool const NoTriggers = _config->FindB("DPkg::NoTriggers", true);
1490
1491 if (RunScripts("DPkg::Pre-Invoke") == false)
1492 return false;
1493
1494 if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
1495 return false;
1496
1497 auto const noopDPkgInvocation = _config->FindB("Debug::pkgDPkgPM",false);
1498 // store auto-bits as they are supposed to be after dpkg is run
1499 if (noopDPkgInvocation == false)
1500 Cache.writeStateFile(NULL);
1501
1502 bool dpkg_recursive_install = _config->FindB("dpkg::install::recursive", false);
1503 if (_config->FindB("dpkg::install::recursive::force", false) == false)
1504 {
1505 // dpkg uses a sorted treewalk since that version which enables the workaround to work
1506 auto const dpkgpkg = Cache.FindPkg("dpkg");
1507 if (likely(dpkgpkg.end() == false && dpkgpkg->CurrentVer != 0))
1508 dpkg_recursive_install = Cache.VS().CmpVersion("1.18.5", dpkgpkg.CurrentVer().VerStr()) <= 0;
1509 }
1510 // no point in doing this dance for a handful of packages only
1511 unsigned int const dpkg_recursive_install_min = _config->FindB("dpkg::install::recursive::minimum", 5);
1512 // FIXME: workaround for dpkg bug, see our ./test-bug-740843-versioned-up-down-breaks test
1513 bool const dpkg_recursive_install_numbered = _config->FindB("dpkg::install::recursive::numbered", true);
1514
1515 // for the progress
1516 BuildPackagesProgressMap();
1517
1518 APT::StateChanges approvedStates;
1519 if (_config->FindB("dpkg::selection::remove::approved", true))
1520 {
1521 for (auto && I : List)
1522 if (I.Op == Item::Purge)
1523 approvedStates.Purge(FindToBeRemovedVersion(I.Pkg));
1524 else if (I.Op == Item::Remove)
1525 approvedStates.Remove(FindToBeRemovedVersion(I.Pkg));
1526 }
1527
1528 // Skip removes if we install another architecture of this package soon (crossgrade)
1529 // We can't just skip them all the time as it could be an ordering requirement [of another package]
1530 if ((approvedStates.Remove().empty() == false || approvedStates.Purge().empty() == false) &&
1531 _config->FindB("dpkg::remove::crossgrade::implicit", true) == true)
1532 {
1533 std::unordered_set<decltype(pkgCache::Package::ID)> crossgraded;
1534 std::vector<std::pair<Item*, std::string>> toCrossgrade;
1535 auto const PlanedEnd = std::next(List.begin(), explicitIdx);
1536 for (auto I = List.begin(); I != PlanedEnd; ++I)
1537 {
1538 if (I->Op != Item::Remove && I->Op != Item::Purge)
1539 continue;
1540
1541 auto const Grp = I->Pkg.Group();
1542 size_t installedInstances = 0, wannabeInstances = 0;
1543 bool multiArchInstances = false;
1544 for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
1545 {
1546 if (Pkg->CurrentVer != 0)
1547 {
1548 ++installedInstances;
1549 if (Cache[Pkg].Delete() == false)
1550 ++wannabeInstances;
1551 }
1552 else if (PackageOps.find(Pkg.FullName()) != PackageOps.end())
1553 ++wannabeInstances;
1554 if (multiArchInstances == false)
1555 {
1556 auto const V = Cache[Pkg].InstVerIter(Cache);
1557 if (V.end() == false && (Pkg->CurrentVer == 0 || V != Pkg.CurrentVer()))
1558 multiArchInstances = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same);
1559 }
1560 }
1561 /* theoretically the installed check would be enough as some wannabe will
1562 be first and hence be the crossgrade we were looking for, but #844300
1563 prevents this so we keep these situations explicit removes.
1564 It is also the reason why neither of them can be a M-A:same package */
1565 if (installedInstances == 1 && wannabeInstances == 1 && multiArchInstances == false)
1566 {
1567 auto const FirstInstall = std::find_if_not(I, List.end(),
1568 [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; });
1569 auto const LastInstall = std::find_if_not(FirstInstall, List.end(),
1570 [](Item const &i) { return i.Op == Item::Install; });
1571 auto const crosser = std::find_if(FirstInstall, LastInstall,
1572 [&I](Item const &i) { return i.Pkg->Group == I->Pkg->Group; });
1573 if (crosser != LastInstall)
1574 {
1575 crossgraded.insert(I->Pkg->ID);
1576 toCrossgrade.emplace_back(&(*I), crosser->Pkg.FullName());
1577 }
1578 }
1579 }
1580 for (auto I = PlanedEnd; I != List.end(); ++I)
1581 {
1582 if (I->Op != Item::Remove && I->Op != Item::Purge)
1583 continue;
1584
1585 auto const Grp = I->Pkg.Group();
1586 for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
1587 {
1588 if (Pkg == I->Pkg || Cache[Pkg].Install() == false)
1589 continue;
1590 toCrossgrade.emplace_back(&(*I), Pkg.FullName());
1591 break;
1592 }
1593 }
1594 for (auto C : toCrossgrade)
1595 {
1596 // we never do purges on packages which are crossgraded, even if "requested"
1597 if (C.first->Op == Item::Purge)
1598 {
1599 C.first->Op = Item::Remove; // crossgrades should never be purged
1600 auto && Purges = approvedStates.Purge();
1601 auto const Ver = std::find_if(
1602 #if __GNUC__ >= 5 || (__GNUC_MINOR__ >= 9 && __GNUC__ >= 4)
1603 Purges.cbegin(), Purges.cend(),
1604 #else
1605 Purges.begin(), Purges.end(),
1606 #endif
1607 [&C](pkgCache::VerIterator const &V) { return V.ParentPkg() == C.first->Pkg; });
1608 approvedStates.Remove(*Ver);
1609 Purges.erase(Ver);
1610 auto && RemOp = PackageOps[C.first->Pkg.FullName()];
1611 if (RemOp.size() == 5)
1612 {
1613 RemOp.erase(std::next(RemOp.begin(), 3), RemOp.end());
1614 PackagesTotal -= 2;
1615 }
1616 else
1617 _error->Warning("Unexpected amount of planned ops for package %s: %lu", C.first->Pkg.FullName().c_str(), RemOp.size());
1618 }
1619 }
1620 if (crossgraded.empty() == false)
1621 {
1622 auto const oldsize = List.size();
1623 List.erase(std::remove_if(List.begin(), PlanedEnd,
1624 [&crossgraded](Item const &i){
1625 return (i.Op == Item::Remove || i.Op == Item::Purge) &&
1626 crossgraded.find(i.Pkg->ID) != crossgraded.end();
1627 }), PlanedEnd);
1628 explicitIdx -= (oldsize - List.size());
1629 }
1630 }
1631
1632 APT::StateChanges currentStates;
1633 if (_config->FindB("dpkg::selection::current::saveandrestore", true))
1634 {
1635 for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
1636 if (Pkg->CurrentVer == 0)
1637 continue;
1638 else if (Pkg->SelectedState == pkgCache::State::Purge)
1639 currentStates.Purge(FindToBeRemovedVersion(Pkg));
1640 else if (Pkg->SelectedState == pkgCache::State::DeInstall)
1641 currentStates.Remove(FindToBeRemovedVersion(Pkg));
1642 if (currentStates.empty() == false)
1643 {
1644 APT::StateChanges cleanStates;
1645 for (auto && P: currentStates.Remove())
1646 cleanStates.Install(P);
1647 for (auto && P: currentStates.Purge())
1648 cleanStates.Install(P);
1649 if (cleanStates.Save(false) == false)
1650 return _error->Error("Couldn't clean the currently selected dpkg states");
1651 }
1652 }
1653
1654 if (_config->FindB("dpkg::selection::remove::approved", true))
1655 {
1656 if (approvedStates.Save(false) == false)
1657 {
1658 _error->Error("Couldn't record the approved state changes as dpkg selection states");
1659 if (currentStates.Save(false) == false)
1660 _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
1661 return false;
1662 }
1663
1664 List.erase(std::next(List.begin(), explicitIdx), List.end());
1665
1666 std::vector<bool> toBeRemoved(Cache.Head().PackageCount, false);
1667 for (auto && I: approvedStates.Remove())
1668 toBeRemoved[I.ParentPkg()->ID] = true;
1669 for (auto && I: approvedStates.Purge())
1670 toBeRemoved[I.ParentPkg()->ID] = true;
1671
1672 for (auto && I: List)
1673 if (I.Op == Item::Remove || I.Op == Item::Purge)
1674 toBeRemoved[I.Pkg->ID] = false;
1675
1676 bool const RemovePending = std::find(toBeRemoved.begin(), toBeRemoved.end(), true) != toBeRemoved.end();
1677 bool const PurgePending = approvedStates.Purge().empty() == false;
1678 if (RemovePending != false || PurgePending != false)
1679 List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
1680 if (RemovePending)
1681 List.emplace_back(Item::RemovePending, pkgCache::PkgIterator());
1682 if (PurgePending)
1683 List.emplace_back(Item::PurgePending, pkgCache::PkgIterator());
1684
1685 // support subpressing of triggers processing for special
1686 // cases like d-i that runs the triggers handling manually
1687 if (_config->FindB("DPkg::ConfigurePending", true))
1688 List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
1689 }
1690 bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
1691
1692 d->stdin_is_dev_null = false;
1693
1694 // create log
1695 OpenLog();
1696
1697 bool dpkgMultiArch = debSystem::SupportsMultiArch();
1698
1699 // start pty magic before the loop
1700 StartPtyMagic();
1701
1702 // Tell the progress that its starting and fork dpkg
1703 d->progress->Start(d->master);
1704
1705 // this loop is runs once per dpkg operation
1706 vector<Item>::const_iterator I = List.cbegin();
1707 while (I != List.end())
1708 {
1709 // Do all actions with the same Op in one run
1710 vector<Item>::const_iterator J = I;
1711 if (TriggersPending == true)
1712 for (; J != List.end(); ++J)
1713 {
1714 if (J->Op == I->Op)
1715 continue;
1716 if (J->Op != Item::TriggersPending)
1717 break;
1718 vector<Item>::const_iterator T = J + 1;
1719 if (T != List.end() && T->Op == I->Op)
1720 continue;
1721 break;
1722 }
1723 else if (J->Op == Item::Remove || J->Op == Item::Purge)
1724 J = std::find_if(J, List.cend(), [](Item const &I) { return I.Op != Item::Remove && I.Op != Item::Purge; });
1725 else
1726 J = std::find_if(J, List.cend(), [&J](Item const &I) { return I.Op != J->Op; });
1727
1728 auto const size = (J - I) + 10;
1729
1730 // start with the baseset of arguments
1731 auto Size = StartSize;
1732 Args.erase(Args.begin() + BaseArgs, Args.end());
1733 Args.reserve(size);
1734 // keep track of allocated strings for multiarch package names
1735 std::vector<char *> Packages(size, nullptr);
1736
1737 int fd[2];
1738 if (pipe(fd) != 0)
1739 return _error->Errno("pipe","Failed to create IPC pipe to dpkg");
1740
1741 #define ADDARG(X) Args.push_back(X); Size += strlen(X)
1742 #define ADDARGC(X) Args.push_back(X); Size += sizeof(X) - 1
1743
1744 ADDARGC("--status-fd");
1745 char status_fd_buf[20];
1746 snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]);
1747 ADDARG(status_fd_buf);
1748 unsigned long const Op = I->Op;
1749
1750 if (NoTriggers == true && I->Op != Item::TriggersPending &&
1751 (I->Op != Item::ConfigurePending || std::next(I) != List.end()))
1752 {
1753 ADDARGC("--no-triggers");
1754 }
1755
1756 switch (I->Op)
1757 {
1758 case Item::Remove:
1759 case Item::Purge:
1760 ADDARGC("--force-depends");
1761 if (std::any_of(I, J, ItemIsEssential))
1762 ADDARGC("--force-remove-essential");
1763 ADDARGC("--remove");
1764 break;
1765
1766 case Item::Configure:
1767 ADDARGC("--configure");
1768 break;
1769
1770 case Item::ConfigurePending:
1771 ADDARGC("--configure");
1772 ADDARGC("--pending");
1773 break;
1774
1775 case Item::TriggersPending:
1776 ADDARGC("--triggers-only");
1777 ADDARGC("--pending");
1778 break;
1779
1780 case Item::RemovePending:
1781 ADDARGC("--remove");
1782 ADDARGC("--pending");
1783 break;
1784
1785 case Item::PurgePending:
1786 ADDARGC("--purge");
1787 ADDARGC("--pending");
1788 break;
1789
1790 case Item::Install:
1791 ADDARGC("--unpack");
1792 ADDARGC("--auto-deconfigure");
1793 break;
1794 }
1795
1796 char * tmpdir_to_free = nullptr;
1797
1798 // Write in the file or package names
1799 if (I->Op == Item::Install)
1800 {
1801 auto const installsToDo = J - I;
1802 if (dpkg_recursive_install == true && dpkg_recursive_install_min < installsToDo)
1803 {
1804 std::string tmpdir;
1805 strprintf(tmpdir, "%s/apt-dpkg-install-XXXXXX", GetTempDir().c_str());
1806 tmpdir_to_free = strndup(tmpdir.data(), tmpdir.length());
1807 if (mkdtemp(tmpdir_to_free) == nullptr)
1808 return _error->Errno("DPkg::Go", "mkdtemp of %s failed in preparation of calling dpkg unpack", tmpdir_to_free);
1809
1810 char p = 1;
1811 for (auto c = installsToDo - 1; (c = c/10) != 0; ++p);
1812 for (unsigned long n = 0; I != J; ++n, ++I)
1813 {
1814 if (I->File[0] != '/')
1815 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
1816 auto file = flNotDir(I->File);
1817 if (flExtension(file) != "deb")
1818 file.append(".deb");
1819 std::string linkpath;
1820 if (dpkg_recursive_install_numbered)
1821 strprintf(linkpath, "%s/%.*lu-%s", tmpdir_to_free, p, n, file.c_str());
1822 else
1823 strprintf(linkpath, "%s/%s", tmpdir_to_free, file.c_str());
1824 if (symlink(I->File.c_str(), linkpath.c_str()) != 0)
1825 return _error->Errno("DPkg::Go", "Symlinking %s to %s failed!", I->File.c_str(), linkpath.c_str());
1826 }
1827 ADDARGC("--recursive");
1828 ADDARG(tmpdir_to_free);
1829 }
1830 else
1831 {
1832 for (;I != J && Size < MaxArgBytes; ++I)
1833 {
1834 if (I->File[0] != '/')
1835 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
1836 Args.push_back(I->File.c_str());
1837 Size += I->File.length();
1838 }
1839 }
1840 }
1841 else if (I->Op == Item::RemovePending)
1842 {
1843 ++I;
1844 StripAlreadyDoneFrom(approvedStates.Remove());
1845 if (approvedStates.Remove().empty())
1846 continue;
1847 }
1848 else if (I->Op == Item::PurgePending)
1849 {
1850 ++I;
1851 // explicit removes of packages without conffiles passthrough the purge states instantly, too.
1852 // Setting these non-installed packages up for purging generates 'unknown pkg' warnings from dpkg
1853 StripAlreadyDoneFrom(approvedStates.Purge());
1854 if (approvedStates.Purge().empty())
1855 continue;
1856 std::remove_reference<decltype(approvedStates.Remove())>::type approvedRemoves;
1857 std::swap(approvedRemoves, approvedStates.Remove());
1858 // we apply it again here as an explicit remove in the ordering will have cleared the purge state
1859 if (approvedStates.Save(false) == false)
1860 {
1861 _error->Error("Couldn't record the approved purges as dpkg selection states");
1862 if (currentStates.Save(false) == false)
1863 _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
1864 return false;
1865 }
1866 std::swap(approvedRemoves, approvedStates.Remove());
1867 }
1868 else
1869 {
1870 string const nativeArch = _config->Find("APT::Architecture");
1871 unsigned long const oldSize = I->Pkg.end() == false ? Size : 0;
1872 for (;I != J && Size < MaxArgBytes; ++I)
1873 {
1874 if((*I).Pkg.end() == true)
1875 continue;
1876 if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.FullName(true)) != disappearedPkgs.end())
1877 continue;
1878 // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian
1879 if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch ||
1880 strcmp(I->Pkg.Arch(), "all") == 0 ||
1881 strcmp(I->Pkg.Arch(), "none") == 0))
1882 {
1883 char const * const name = I->Pkg.Name();
1884 ADDARG(name);
1885 }
1886 else
1887 {
1888 pkgCache::VerIterator PkgVer;
1889 std::string name = I->Pkg.Name();
1890 if (Op == Item::Remove)
1891 PkgVer = I->Pkg.CurrentVer();
1892 else if (Op == Item::Purge)
1893 {
1894 // we purge later with --purge --pending, so if it isn't installed (aka rc-only), skip it here
1895 PkgVer = I->Pkg.CurrentVer();
1896 if (PkgVer.end() == true)
1897 continue;
1898 }
1899 else
1900 PkgVer = Cache[I->Pkg].InstVerIter(Cache);
1901 if (strcmp(I->Pkg.Arch(), "none") == 0)
1902 ; // never arch-qualify a package without an arch
1903 else if (PkgVer.end() == false)
1904 name.append(":").append(PkgVer.Arch());
1905 else
1906 _error->Warning("Can not find PkgVer for '%s'", name.c_str());
1907 char * const fullname = strdup(name.c_str());
1908 Packages.push_back(fullname);
1909 ADDARG(fullname);
1910 }
1911 }
1912 // skip configure action if all sheduled packages disappeared
1913 if (oldSize == Size)
1914 continue;
1915 }
1916 #undef ADDARGC
1917 #undef ADDARG
1918
1919 J = I;
1920
1921 if (noopDPkgInvocation == true)
1922 {
1923 for (std::vector<const char *>::const_iterator a = Args.begin();
1924 a != Args.end(); ++a)
1925 clog << *a << ' ';
1926 clog << endl;
1927 for (std::vector<char *>::const_iterator p = Packages.begin();
1928 p != Packages.end(); ++p)
1929 free(*p);
1930 Packages.clear();
1931 close(fd[0]);
1932 close(fd[1]);
1933 cleanUpTmpDir(tmpdir_to_free);
1934 continue;
1935 }
1936 Args.push_back(NULL);
1937
1938 cout << flush;
1939 clog << flush;
1940 cerr << flush;
1941
1942 /* Mask off sig int/quit. We do this because dpkg also does when
1943 it forks scripts. What happens is that when you hit ctrl-c it sends
1944 it to all processes in the group. Since dpkg ignores the signal
1945 it doesn't die but we do! So we must also ignore it */
1946 sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
1947 sighandler_t old_SIGINT = signal(SIGINT,SigINT);
1948
1949 // Check here for any SIGINT
1950 if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install))
1951 break;
1952
1953 // ignore SIGHUP as well (debian #463030)
1954 sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN);
1955
1956 // now run dpkg
1957 d->progress->StartDpkg();
1958 std::set<int> KeepFDs;
1959 KeepFDs.insert(fd[1]);
1960 MergeKeepFdsFromConfiguration(KeepFDs);
1961 pid_t Child = ExecFork(KeepFDs);
1962 if (Child == 0)
1963 {
1964 // This is the child
1965 SetupSlavePtyMagic();
1966 close(fd[0]); // close the read end of the pipe
1967
1968 debSystem::DpkgChrootDirectory();
1969
1970 if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
1971 _exit(100);
1972
1973 if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
1974 {
1975 int Flags;
1976 int dummy = 0;
1977 if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
1978 _exit(100);
1979
1980 // Discard everything in stdin before forking dpkg
1981 if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
1982 _exit(100);
1983
1984 while (read(STDIN_FILENO,&dummy,1) == 1);
1985
1986 if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
1987 _exit(100);
1988 }
1989
1990 // if color support isn't enabled/disabled explicitly tell
1991 // dpkg to use the same state apt is using for its color support
1992 if (_config->FindB("APT::Color", false) == true)
1993 setenv("DPKG_COLORS", "always", 0);
1994 else
1995 setenv("DPKG_COLORS", "never", 0);
1996
1997 execvp(Args[0], (char**) &Args[0]);
1998 cerr << "Could not exec dpkg!" << endl;
1999 _exit(100);
2000 }
2001
2002 // we read from dpkg here
2003 int const _dpkgin = fd[0];
2004 close(fd[1]); // close the write end of the pipe
2005
2006 // apply ionice
2007 if (_config->FindB("DPkg::UseIoNice", false) == true)
2008 ionice(Child);
2009
2010 // setups fds
2011 sigemptyset(&d->sigmask);
2012 sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask);
2013
2014 /* free vectors (and therefore memory) as we don't need the included data anymore */
2015 for (std::vector<char *>::const_iterator p = Packages.begin();
2016 p != Packages.end(); ++p)
2017 free(*p);
2018 Packages.clear();
2019
2020 // the result of the waitpid call
2021 int Status = 0;
2022 int res;
2023 bool waitpid_failure = false;
2024 while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
2025 if(res < 0) {
2026 // error handling, waitpid returned -1
2027 if (errno == EINTR)
2028 continue;
2029 waitpid_failure = true;
2030 break;
2031 }
2032
2033 // wait for input or output here
2034 FD_ZERO(&rfds);
2035 if (d->master >= 0 && d->direct_stdin == false && d->stdin_is_dev_null == false)
2036 FD_SET(STDIN_FILENO, &rfds);
2037 FD_SET(_dpkgin, &rfds);
2038 if(d->master >= 0)
2039 FD_SET(d->master, &rfds);
2040 tv.tv_sec = 0;
2041 tv.tv_nsec = d->progress->GetPulseInterval();
2042 auto const select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL,
2043 &tv, &d->original_sigmask);
2044 d->progress->Pulse();
2045 if (select_ret == 0)
2046 continue;
2047 else if (select_ret < 0 && errno == EINTR)
2048 continue;
2049 else if (select_ret < 0)
2050 {
2051 perror("select() returned error");
2052 continue;
2053 }
2054
2055 if(d->master >= 0 && FD_ISSET(d->master, &rfds))
2056 DoTerminalPty(d->master);
2057 if(d->master >= 0 && FD_ISSET(0, &rfds))
2058 DoStdin(d->master);
2059 if(FD_ISSET(_dpkgin, &rfds))
2060 DoDpkgStatusFd(_dpkgin);
2061 }
2062 close(_dpkgin);
2063
2064 // Restore sig int/quit
2065 signal(SIGQUIT,old_SIGQUIT);
2066 signal(SIGINT,old_SIGINT);
2067 signal(SIGHUP,old_SIGHUP);
2068
2069 cleanUpTmpDir(tmpdir_to_free);
2070
2071 if (waitpid_failure == true)
2072 {
2073 strprintf(d->dpkg_error, "Sub-process %s couldn't be waited for.",Args[0]);
2074 _error->Error("%s", d->dpkg_error.c_str());
2075 break;
2076 }
2077
2078 // Check for an error code.
2079 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
2080 {
2081 // if it was set to "keep-dpkg-running" then we won't return
2082 // here but keep the loop going and just report it as a error
2083 // for later
2084 bool const stopOnError = _config->FindB("Dpkg::StopOnError",true);
2085
2086 if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
2087 strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]);
2088 else if (WIFEXITED(Status) != 0)
2089 strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status));
2090 else
2091 strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]);
2092 _error->Error("%s", d->dpkg_error.c_str());
2093
2094 if(stopOnError)
2095 break;
2096 }
2097 }
2098 // dpkg is done at this point
2099 StopPtyMagic();
2100 CloseLog();
2101
2102 if (d->dpkg_error.empty() == false)
2103 {
2104 // no point in reseting packages we already completed removal for
2105 StripAlreadyDoneFrom(approvedStates.Remove());
2106 StripAlreadyDoneFrom(approvedStates.Purge());
2107 APT::StateChanges undo;
2108 auto && undoRem = approvedStates.Remove();
2109 std::move(undoRem.begin(), undoRem.end(), std::back_inserter(undo.Install()));
2110 auto && undoPur = approvedStates.Purge();
2111 std::move(undoPur.begin(), undoPur.end(), std::back_inserter(undo.Install()));
2112 approvedStates.clear();
2113 if (undo.Save(false) == false)
2114 _error->Error("Couldn't revert dpkg selection for approved remove/purge after an error was encountered!");
2115 }
2116
2117 StripAlreadyDoneFrom(currentStates.Remove());
2118 StripAlreadyDoneFrom(currentStates.Purge());
2119 if (currentStates.Save(false) == false)
2120 _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
2121
2122 if (pkgPackageManager::SigINTStop)
2123 _error->Warning(_("Operation was interrupted before it could finish"));
2124
2125 if (noopDPkgInvocation == false)
2126 {
2127 if (d->dpkg_error.empty() && (PackagesDone + 1) != PackagesTotal)
2128 {
2129 std::string pkglist;
2130 for (auto const &PO: PackageOps)
2131 if (PO.second.size() != PackageOpsDone[PO.first])
2132 {
2133 if (pkglist.empty() == false)
2134 pkglist.append(" ");
2135 pkglist.append(PO.first);
2136 }
2137 /* who cares about correct progress? As we depend on it for skipping actions
2138 our parsing should be correct. People will no doubt be confused if they see
2139 this message, but the dpkg warning about unknown packages isn't much better
2140 from a user POV and combined we might have a chance to figure out what is wrong */
2141 _error->Warning("APT had planned for dpkg to do more than it reported back (%u vs %u).\n"
2142 "Affected packages: %s", PackagesDone, PackagesTotal, pkglist.c_str());
2143 }
2144
2145 std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache");
2146 if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true &&
2147 RemoveFile("pkgDPkgPM::Go", oldpkgcache))
2148 {
2149 std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache");
2150 if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true)
2151 {
2152 _error->PushToStack();
2153 pkgCacheFile CacheFile;
2154 CacheFile.BuildCaches(NULL, true);
2155 _error->RevertToStack();
2156 }
2157 }
2158 }
2159
2160 // disappearing packages can forward their auto-bit
2161 if (disappearedPkgs.empty() == false)
2162 Cache.writeStateFile(NULL);
2163
2164 d->progress->Stop();
2165
2166 if (RunScripts("DPkg::Post-Invoke") == false)
2167 return false;
2168
2169 return d->dpkg_error.empty();
2170 }
2171
2172 void SigINT(int /*sig*/) {
2173 pkgPackageManager::SigINTStop = true;
2174 }
2175 /*}}}*/
2176 // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/
2177 // ---------------------------------------------------------------------
2178 /* */
2179 void pkgDPkgPM::Reset()
2180 {
2181 List.erase(List.begin(),List.end());
2182 }
2183 /*}}}*/
2184 // pkgDpkgPM::WriteApportReport - write out error report pkg failure /*{{{*/
2185 // ---------------------------------------------------------------------
2186 /* */
2187 void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
2188 {
2189 // If apport doesn't exist or isn't installed do nothing
2190 // This e.g. prevents messages in 'universes' without apport
2191 pkgCache::PkgIterator apportPkg = Cache.FindPkg("apport");
2192 if (apportPkg.end() == true || apportPkg->CurrentVer == 0)
2193 return;
2194
2195 string pkgname, reportfile, pkgver, arch;
2196 string::size_type pos;
2197 FILE *report;
2198
2199 if (_config->FindB("Dpkg::ApportFailureReport", true) == false)
2200 {
2201 std::clog << "configured to not write apport reports" << std::endl;
2202 return;
2203 }
2204
2205 // only report the first errors
2206 if(pkgFailures > _config->FindI("APT::Apport::MaxReports", 3))
2207 {
2208 std::clog << _("No apport report written because MaxReports is reached already") << std::endl;
2209 return;
2210 }
2211
2212 // check if its not a follow up error
2213 const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured");
2214 if(strstr(errormsg, needle) != NULL) {
2215 std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl;
2216 return;
2217 }
2218
2219 // do not report disk-full failures
2220 if(strstr(errormsg, strerror(ENOSPC)) != NULL) {
2221 std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl;
2222 return;
2223 }
2224
2225 // do not report out-of-memory failures
2226 if(strstr(errormsg, strerror(ENOMEM)) != NULL ||
2227 strstr(errormsg, "failed to allocate memory") != NULL) {
2228 std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl;
2229 return;
2230 }
2231
2232 // do not report bugs regarding inaccessible local files
2233 if(strstr(errormsg, strerror(ENOENT)) != NULL ||
2234 strstr(errormsg, "cannot access archive") != NULL) {
2235 std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
2236 return;
2237 }
2238
2239 // do not report errors encountered when decompressing packages
2240 if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) {
2241 std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
2242 return;
2243 }
2244
2245 // do not report dpkg I/O errors, this is a format string, so we compare
2246 // the prefix and the suffix of the error with the dpkg error message
2247 vector<string> io_errors;
2248 io_errors.push_back(string("failed to read"));
2249 io_errors.push_back(string("failed to write"));
2250 io_errors.push_back(string("failed to seek"));
2251 io_errors.push_back(string("unexpected end of file or stream"));
2252
2253 for (vector<string>::iterator I = io_errors.begin(); I != io_errors.end(); ++I)
2254 {
2255 vector<string> list = VectorizeString(dgettext("dpkg", (*I).c_str()), '%');
2256 if (list.size() > 1) {
2257 // we need to split %s, VectorizeString only allows char so we need
2258 // to kill the "s" manually
2259 if (list[1].size() > 1) {
2260 list[1].erase(0, 1);
2261 if(strstr(errormsg, list[0].c_str()) &&
2262 strstr(errormsg, list[1].c_str())) {
2263 std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl;
2264 return;
2265 }
2266 }
2267 }
2268 }
2269
2270 // get the pkgname and reportfile
2271 pkgname = flNotDir(pkgpath);
2272 pos = pkgname.find('_');
2273 if(pos != string::npos)
2274 pkgname = pkgname.substr(0, pos);
2275
2276 // find the package version and source package name
2277 pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
2278 if (Pkg.end() == true)
2279 {
2280 if (pos == std::string::npos || _config->FindB("dpkg::install::recursive::numbered", true) == false)
2281 return;
2282 auto const dash = pkgname.find_first_not_of("0123456789");
2283 if (dash == std::string::npos || pkgname[dash] != '-')
2284 return;
2285 pkgname.erase(0, dash + 1);
2286 Pkg = Cache.FindPkg(pkgname);
2287 if (Pkg.end() == true)
2288 return;
2289 }
2290 pkgCache::VerIterator Ver = Cache.GetCandidateVersion(Pkg);
2291 if (Ver.end() == true)
2292 return;
2293 pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr();
2294
2295 // if the file exists already, we check:
2296 // - if it was reported already (touched by apport).
2297 // If not, we do nothing, otherwise
2298 // we overwrite it. This is the same behaviour as apport
2299 // - if we have a report with the same pkgversion already
2300 // then we skip it
2301 _config->CndSet("Dir::Apport", "var/crash");
2302 reportfile = flCombine(_config->FindDir("Dir::Apport", "var/crash"), pkgname+".0.crash");
2303 if(FileExists(reportfile))
2304 {
2305 struct stat buf;
2306 char strbuf[255];
2307
2308 // check atime/mtime
2309 stat(reportfile.c_str(), &buf);
2310 if(buf.st_mtime > buf.st_atime)
2311 return;
2312
2313 // check if the existing report is the same version
2314 report = fopen(reportfile.c_str(),"r");
2315 while(fgets(strbuf, sizeof(strbuf), report) != NULL)
2316 {
2317 if(strstr(strbuf,"Package:") == strbuf)
2318 {
2319 char pkgname[255], version[255];
2320 if(sscanf(strbuf, "Package: %254s %254s", pkgname, version) == 2)
2321 if(strcmp(pkgver.c_str(), version) == 0)
2322 {
2323 fclose(report);
2324 return;
2325 }
2326 }
2327 }
2328 fclose(report);
2329 }
2330
2331 // now write the report
2332 arch = _config->Find("APT::Architecture");
2333 report = fopen(reportfile.c_str(),"w");
2334 if(report == NULL)
2335 return;
2336 if(_config->FindB("DPkgPM::InitialReportOnly",false) == true)
2337 chmod(reportfile.c_str(), 0);
2338 else
2339 chmod(reportfile.c_str(), 0600);
2340 fprintf(report, "ProblemType: Package\n");
2341 fprintf(report, "Architecture: %s\n", arch.c_str());
2342 time_t now = time(NULL);
2343 char ctime_buf[26]; // need at least 26 bytes according to ctime(3)
2344 fprintf(report, "Date: %s" , ctime_r(&now, ctime_buf));
2345 fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str());
2346 fprintf(report, "SourcePackage: %s\n", Ver.SourcePkgName());
2347 fprintf(report, "ErrorMessage:\n %s\n", errormsg);
2348
2349 // ensure that the log is flushed
2350 if(d->term_out)
2351 fflush(d->term_out);
2352
2353 // attach terminal log it if we have it
2354 string logfile_name = _config->FindFile("Dir::Log::Terminal");
2355 if (!logfile_name.empty())
2356 {
2357 FILE *log = NULL;
2358
2359 fprintf(report, "DpkgTerminalLog:\n");
2360 log = fopen(logfile_name.c_str(),"r");
2361 if(log != NULL)
2362 {
2363 char buf[1024];
2364 while( fgets(buf, sizeof(buf), log) != NULL)
2365 fprintf(report, " %s", buf);
2366 fprintf(report, " \n");
2367 fclose(log);
2368 }
2369 }
2370
2371 // attach history log it if we have it
2372 string histfile_name = _config->FindFile("Dir::Log::History");
2373 if (!histfile_name.empty())
2374 {
2375 fprintf(report, "DpkgHistoryLog:\n");
2376 FILE* log = fopen(histfile_name.c_str(),"r");
2377 if(log != NULL)
2378 {
2379 char buf[1024];
2380 while( fgets(buf, sizeof(buf), log) != NULL)
2381 fprintf(report, " %s", buf);
2382 fclose(log);
2383 }
2384 }
2385
2386 // log the ordering, see dpkgpm.h and the "Ops" enum there
2387 fprintf(report, "AptOrdering:\n");
2388 for (auto && I : List)
2389 {
2390 char const * opstr = nullptr;
2391 switch (I.Op)
2392 {
2393 case Item::Install: opstr = "Install"; break;
2394 case Item::Configure: opstr = "Configure"; break;
2395 case Item::Remove: opstr = "Remove"; break;
2396 case Item::Purge: opstr = "Purge"; break;
2397 case Item::ConfigurePending: opstr = "ConfigurePending"; break;
2398 case Item::TriggersPending: opstr = "TriggersPending"; break;
2399 case Item::RemovePending: opstr = "RemovePending"; break;
2400 case Item::PurgePending: opstr = "PurgePending"; break;
2401 }
2402 auto const pkgname = I.Pkg.end() ? "NULL" : I.Pkg.FullName();
2403 fprintf(report, " %s: %s\n", pkgname.c_str(), opstr);
2404 }
2405
2406 // attach dmesg log (to learn about segfaults)
2407 if (FileExists("/bin/dmesg"))
2408 {
2409 fprintf(report, "Dmesg:\n");
2410 FILE *log = popen("/bin/dmesg","r");
2411 if(log != NULL)
2412 {
2413 char buf[1024];
2414 while( fgets(buf, sizeof(buf), log) != NULL)
2415 fprintf(report, " %s", buf);
2416 pclose(log);
2417 }
2418 }
2419
2420 // attach df -l log (to learn about filesystem status)
2421 if (FileExists("/bin/df"))
2422 {
2423
2424 fprintf(report, "Df:\n");
2425 FILE *log = popen("/bin/df -l","r");
2426 if(log != NULL)
2427 {
2428 char buf[1024];
2429 while( fgets(buf, sizeof(buf), log) != NULL)
2430 fprintf(report, " %s", buf);
2431 pclose(log);
2432 }
2433 }
2434
2435 fclose(report);
2436
2437 }
2438 /*}}}*/