]> git.saurik.com Git - apt.git/blob - apt-pkg/deb/dpkgpm.cc
7a02533838fa0fb3bb82ecf56fc50b1e77d4bfcd
[apt.git] / apt-pkg / deb / dpkgpm.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $
4 /* ######################################################################
5
6 DPKG Package Manager - Provide an interface to dpkg
7
8 ##################################################################### */
9 /*}}}*/
10 // Includes /*{{{*/
11 #include <config.h>
12
13 #include <apt-pkg/cachefile.h>
14 #include <apt-pkg/configuration.h>
15 #include <apt-pkg/depcache.h>
16 #include <apt-pkg/dpkgpm.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/cacheiterators.h>
23 #include <apt-pkg/macros.h>
24 #include <apt-pkg/pkgcache.h>
25
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <grp.h>
29 #include <pwd.h>
30 #include <signal.h>
31 #include <stddef.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <sys/ioctl.h>
35 #include <sys/select.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/wait.h>
39 #include <termios.h>
40 #include <time.h>
41 #include <unistd.h>
42 #include <algorithm>
43 #include <cstring>
44 #include <iostream>
45 #include <map>
46 #include <set>
47 #include <string>
48 #include <utility>
49 #include <vector>
50
51 #include <apti18n.h>
52 /*}}}*/
53
54 using namespace std;
55
56 APT_PURE static unsigned int
57 EnvironmentSize()
58 {
59 unsigned int size = 0;
60 char **envp = environ;
61
62 while (*envp != NULL)
63 size += strlen (*envp++) + 1;
64
65 return size;
66 }
67
68 class pkgDPkgPMPrivate
69 {
70 public:
71 pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0),
72 term_out(NULL), history_out(NULL),
73 progress(NULL), tt_is_valid(false), master(-1),
74 slave(NULL), protect_slave_from_dying(-1),
75 direct_stdin(false)
76 {
77 dpkgbuf[0] = '\0';
78 }
79 ~pkgDPkgPMPrivate()
80 {
81 }
82 bool stdin_is_dev_null;
83 // the buffer we use for the dpkg status-fd reading
84 char dpkgbuf[1024];
85 int dpkgbuf_pos;
86 FILE *term_out;
87 FILE *history_out;
88 string dpkg_error;
89 APT::Progress::PackageManager *progress;
90
91 // pty stuff
92 struct termios tt;
93 bool tt_is_valid;
94 int master;
95 char * slave;
96 int protect_slave_from_dying;
97
98 // signals
99 sigset_t sigmask;
100 sigset_t original_sigmask;
101
102 bool direct_stdin;
103 };
104
105 namespace
106 {
107 // Maps the dpkg "processing" info to human readable names. Entry 0
108 // of each array is the key, entry 1 is the value.
109 const std::pair<const char *, const char *> PackageProcessingOps[] = {
110 std::make_pair("install", N_("Installing %s")),
111 std::make_pair("configure", N_("Configuring %s")),
112 std::make_pair("remove", N_("Removing %s")),
113 std::make_pair("purge", N_("Completely removing %s")),
114 std::make_pair("disappear", N_("Noting disappearance of %s")),
115 std::make_pair("trigproc", N_("Running post-installation trigger %s"))
116 };
117
118 const std::pair<const char *, const char *> * const PackageProcessingOpsBegin = PackageProcessingOps;
119 const std::pair<const char *, const char *> * const PackageProcessingOpsEnd = PackageProcessingOps + sizeof(PackageProcessingOps) / sizeof(PackageProcessingOps[0]);
120
121 // Predicate to test whether an entry in the PackageProcessingOps
122 // array matches a string.
123 class MatchProcessingOp
124 {
125 const char *target;
126
127 public:
128 MatchProcessingOp(const char *the_target)
129 : target(the_target)
130 {
131 }
132
133 bool operator()(const std::pair<const char *, const char *> &pair) const
134 {
135 return strcmp(pair.first, target) == 0;
136 }
137 };
138 }
139
140 /* helper function to ionice the given PID
141
142 there is no C header for ionice yet - just the syscall interface
143 so we use the binary from util-linux
144 */
145 static bool
146 ionice(int PID)
147 {
148 if (!FileExists("/usr/bin/ionice"))
149 return false;
150 pid_t Process = ExecFork();
151 if (Process == 0)
152 {
153 char buf[32];
154 snprintf(buf, sizeof(buf), "-p%d", PID);
155 const char *Args[4];
156 Args[0] = "/usr/bin/ionice";
157 Args[1] = "-c3";
158 Args[2] = buf;
159 Args[3] = 0;
160 execv(Args[0], (char **)Args);
161 }
162 return ExecWait(Process, "ionice");
163 }
164
165 static std::string getDpkgExecutable()
166 {
167 string Tmp = _config->Find("Dir::Bin::dpkg","dpkg");
168 string const dpkgChrootDir = _config->FindDir("DPkg::Chroot-Directory", "/");
169 size_t dpkgChrootLen = dpkgChrootDir.length();
170 if (dpkgChrootDir != "/" && Tmp.find(dpkgChrootDir) == 0)
171 {
172 if (dpkgChrootDir[dpkgChrootLen - 1] == '/')
173 --dpkgChrootLen;
174 Tmp = Tmp.substr(dpkgChrootLen);
175 }
176 return Tmp;
177 }
178
179 // dpkgChrootDirectory - chrooting for dpkg if needed /*{{{*/
180 static void dpkgChrootDirectory()
181 {
182 std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory");
183 if (chrootDir == "/")
184 return;
185 std::cerr << "Chrooting into " << chrootDir << std::endl;
186 if (chroot(chrootDir.c_str()) != 0)
187 _exit(100);
188 if (chdir("/") != 0)
189 _exit(100);
190 }
191 /*}}}*/
192
193
194 // FindNowVersion - Helper to find a Version in "now" state /*{{{*/
195 // ---------------------------------------------------------------------
196 /* This is helpful when a package is no longer installed but has residual
197 * config files
198 */
199 static
200 pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
201 {
202 pkgCache::VerIterator Ver;
203 for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
204 for (pkgCache::VerFileIterator Vf = Ver.FileList(); Vf.end() == false; ++Vf)
205 for (pkgCache::PkgFileIterator F = Vf.File(); F.end() == false; ++F)
206 {
207 if (F.Archive() != 0 && strcmp(F.Archive(), "now") == 0)
208 return Ver;
209 }
210 return Ver;
211 }
212 /*}}}*/
213
214 // DPkgPM::pkgDPkgPM - Constructor /*{{{*/
215 // ---------------------------------------------------------------------
216 /* */
217 pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache)
218 : pkgPackageManager(Cache),d(new pkgDPkgPMPrivate()), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
219 {
220 }
221 /*}}}*/
222 // DPkgPM::pkgDPkgPM - Destructor /*{{{*/
223 // ---------------------------------------------------------------------
224 /* */
225 pkgDPkgPM::~pkgDPkgPM()
226 {
227 delete d;
228 }
229 /*}}}*/
230 // DPkgPM::Install - Install a package /*{{{*/
231 // ---------------------------------------------------------------------
232 /* Add an install operation to the sequence list */
233 bool pkgDPkgPM::Install(PkgIterator Pkg,string File)
234 {
235 if (File.empty() == true || Pkg.end() == true)
236 return _error->Error("Internal Error, No file name for %s",Pkg.FullName().c_str());
237
238 // If the filename string begins with DPkg::Chroot-Directory, return the
239 // substr that is within the chroot so dpkg can access it.
240 string const chrootdir = _config->FindDir("DPkg::Chroot-Directory","/");
241 if (chrootdir != "/" && File.find(chrootdir) == 0)
242 {
243 size_t len = chrootdir.length();
244 if (chrootdir.at(len - 1) == '/')
245 len--;
246 List.push_back(Item(Item::Install,Pkg,File.substr(len)));
247 }
248 else
249 List.push_back(Item(Item::Install,Pkg,File));
250
251 return true;
252 }
253 /*}}}*/
254 // DPkgPM::Configure - Configure a package /*{{{*/
255 // ---------------------------------------------------------------------
256 /* Add a configure operation to the sequence list */
257 bool pkgDPkgPM::Configure(PkgIterator Pkg)
258 {
259 if (Pkg.end() == true)
260 return false;
261
262 List.push_back(Item(Item::Configure, Pkg));
263
264 // Use triggers for config calls if we configure "smart"
265 // as otherwise Pre-Depends will not be satisfied, see #526774
266 if (_config->FindB("DPkg::TriggersPending", false) == true)
267 List.push_back(Item(Item::TriggersPending, PkgIterator()));
268
269 return true;
270 }
271 /*}}}*/
272 // DPkgPM::Remove - Remove a package /*{{{*/
273 // ---------------------------------------------------------------------
274 /* Add a remove operation to the sequence list */
275 bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge)
276 {
277 if (Pkg.end() == true)
278 return false;
279
280 if (Purge == true)
281 List.push_back(Item(Item::Purge,Pkg));
282 else
283 List.push_back(Item(Item::Remove,Pkg));
284 return true;
285 }
286 /*}}}*/
287 // DPkgPM::SendPkgInfo - Send info for install-pkgs hook /*{{{*/
288 // ---------------------------------------------------------------------
289 /* This is part of the helper script communication interface, it sends
290 very complete information down to the other end of the pipe.*/
291 bool pkgDPkgPM::SendV2Pkgs(FILE *F)
292 {
293 return SendPkgsInfo(F, 2);
294 }
295 bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
296 {
297 // This version of APT supports only v3, so don't sent higher versions
298 if (Version <= 3)
299 fprintf(F,"VERSION %u\n", Version);
300 else
301 fprintf(F,"VERSION 3\n");
302
303 /* Write out all of the configuration directives by walking the
304 configuration tree */
305 const Configuration::Item *Top = _config->Tree(0);
306 for (; Top != 0;)
307 {
308 if (Top->Value.empty() == false)
309 {
310 fprintf(F,"%s=%s\n",
311 QuoteString(Top->FullTag(),"=\"\n").c_str(),
312 QuoteString(Top->Value,"\n").c_str());
313 }
314
315 if (Top->Child != 0)
316 {
317 Top = Top->Child;
318 continue;
319 }
320
321 while (Top != 0 && Top->Next == 0)
322 Top = Top->Parent;
323 if (Top != 0)
324 Top = Top->Next;
325 }
326 fprintf(F,"\n");
327
328 // Write out the package actions in order.
329 for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
330 {
331 if(I->Pkg.end() == true)
332 continue;
333
334 pkgDepCache::StateCache &S = Cache[I->Pkg];
335
336 fprintf(F,"%s ",I->Pkg.Name());
337
338 // Current version which we are going to replace
339 pkgCache::VerIterator CurVer = I->Pkg.CurrentVer();
340 if (CurVer.end() == true && (I->Op == Item::Remove || I->Op == Item::Purge))
341 CurVer = FindNowVersion(I->Pkg);
342
343 if (CurVer.end() == true)
344 {
345 if (Version <= 2)
346 fprintf(F, "- ");
347 else
348 fprintf(F, "- - none ");
349 }
350 else
351 {
352 fprintf(F, "%s ", CurVer.VerStr());
353 if (Version >= 3)
354 fprintf(F, "%s %s ", CurVer.Arch(), CurVer.MultiArchType());
355 }
356
357 // Show the compare operator between current and install version
358 if (S.InstallVer != 0)
359 {
360 pkgCache::VerIterator const InstVer = S.InstVerIter(Cache);
361 int Comp = 2;
362 if (CurVer.end() == false)
363 Comp = InstVer.CompareVer(CurVer);
364 if (Comp < 0)
365 fprintf(F,"> ");
366 else if (Comp == 0)
367 fprintf(F,"= ");
368 else if (Comp > 0)
369 fprintf(F,"< ");
370 fprintf(F, "%s ", InstVer.VerStr());
371 if (Version >= 3)
372 fprintf(F, "%s %s ", InstVer.Arch(), InstVer.MultiArchType());
373 }
374 else
375 {
376 if (Version <= 2)
377 fprintf(F, "> - ");
378 else
379 fprintf(F, "> - - none ");
380 }
381
382 // Show the filename/operation
383 if (I->Op == Item::Install)
384 {
385 // No errors here..
386 if (I->File[0] != '/')
387 fprintf(F,"**ERROR**\n");
388 else
389 fprintf(F,"%s\n",I->File.c_str());
390 }
391 else if (I->Op == Item::Configure)
392 fprintf(F,"**CONFIGURE**\n");
393 else if (I->Op == Item::Remove ||
394 I->Op == Item::Purge)
395 fprintf(F,"**REMOVE**\n");
396
397 if (ferror(F) != 0)
398 return false;
399 }
400 return true;
401 }
402 /*}}}*/
403 // DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
404 // ---------------------------------------------------------------------
405 /* This looks for a list of scripts to run from the configuration file
406 each one is run and is fed on standard input a list of all .deb files
407 that are due to be installed. */
408 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
409 {
410 bool result = true;
411
412 Configuration::Item const *Opts = _config->Tree(Cnf);
413 if (Opts == 0 || Opts->Child == 0)
414 return true;
415 Opts = Opts->Child;
416
417 sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN);
418
419 unsigned int Count = 1;
420 for (; Opts != 0; Opts = Opts->Next, Count++)
421 {
422 if (Opts->Value.empty() == true)
423 continue;
424
425 if(_config->FindB("Debug::RunScripts", false) == true)
426 std::clog << "Running external script with list of all .deb file: '"
427 << Opts->Value << "'" << std::endl;
428
429 // Determine the protocol version
430 string OptSec = Opts->Value;
431 string::size_type Pos;
432 if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
433 Pos = OptSec.length();
434 OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
435
436 unsigned int Version = _config->FindI(OptSec+"::Version",1);
437 unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
438
439 // Create the pipes
440 std::set<int> KeepFDs;
441 MergeKeepFdsFromConfiguration(KeepFDs);
442 int Pipes[2];
443 if (pipe(Pipes) != 0) {
444 result = _error->Errno("pipe","Failed to create IPC pipe to subprocess");
445 break;
446 }
447 if (InfoFD != (unsigned)Pipes[0])
448 SetCloseExec(Pipes[0],true);
449 else
450 KeepFDs.insert(Pipes[0]);
451
452
453 SetCloseExec(Pipes[1],true);
454
455 // Purified Fork for running the script
456 pid_t Process = ExecFork(KeepFDs);
457 if (Process == 0)
458 {
459 // Setup the FDs
460 dup2(Pipes[0], InfoFD);
461 SetCloseExec(STDOUT_FILENO,false);
462 SetCloseExec(STDIN_FILENO,false);
463 SetCloseExec(STDERR_FILENO,false);
464
465 string hookfd;
466 strprintf(hookfd, "%d", InfoFD);
467 setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1);
468
469 dpkgChrootDirectory();
470 const char *Args[4];
471 Args[0] = "/bin/sh";
472 Args[1] = "-c";
473 Args[2] = Opts->Value.c_str();
474 Args[3] = 0;
475 execv(Args[0],(char **)Args);
476 _exit(100);
477 }
478 close(Pipes[0]);
479 FILE *F = fdopen(Pipes[1],"w");
480 if (F == 0) {
481 result = _error->Errno("fdopen","Faild to open new FD");
482 break;
483 }
484
485 // Feed it the filenames.
486 if (Version <= 1)
487 {
488 for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
489 {
490 // Only deal with packages to be installed from .deb
491 if (I->Op != Item::Install)
492 continue;
493
494 // No errors here..
495 if (I->File[0] != '/')
496 continue;
497
498 /* Feed the filename of each package that is pending install
499 into the pipe. */
500 fprintf(F,"%s\n",I->File.c_str());
501 if (ferror(F) != 0)
502 break;
503 }
504 }
505 else
506 SendPkgsInfo(F, Version);
507
508 fclose(F);
509
510 // Clean up the sub process
511 if (ExecWait(Process,Opts->Value.c_str()) == false) {
512 result = _error->Error("Failure running script %s",Opts->Value.c_str());
513 break;
514 }
515 }
516 signal(SIGPIPE, old_sigpipe);
517
518 return result;
519 }
520 /*}}}*/
521 // DPkgPM::DoStdin - Read stdin and pass to master pty /*{{{*/
522 // ---------------------------------------------------------------------
523 /*
524 */
525 void pkgDPkgPM::DoStdin(int master)
526 {
527 unsigned char input_buf[256] = {0,};
528 ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf));
529 if (len)
530 FileFd::Write(master, input_buf, len);
531 else
532 d->stdin_is_dev_null = true;
533 }
534 /*}}}*/
535 // DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/
536 // ---------------------------------------------------------------------
537 /*
538 * read the terminal pty and write log
539 */
540 void pkgDPkgPM::DoTerminalPty(int master)
541 {
542 unsigned char term_buf[1024] = {0,0, };
543
544 ssize_t len=read(master, term_buf, sizeof(term_buf));
545 if(len == -1 && errno == EIO)
546 {
547 // this happens when the child is about to exit, we
548 // give it time to actually exit, otherwise we run
549 // into a race so we sleep for half a second.
550 struct timespec sleepfor = { 0, 500000000 };
551 nanosleep(&sleepfor, NULL);
552 return;
553 }
554 if(len <= 0)
555 return;
556 FileFd::Write(1, term_buf, len);
557 if(d->term_out)
558 fwrite(term_buf, len, sizeof(char), d->term_out);
559 }
560 /*}}}*/
561 // DPkgPM::ProcessDpkgStatusBuf /*{{{*/
562 // ---------------------------------------------------------------------
563 /*
564 */
565 void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
566 {
567 bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false);
568 if (Debug == true)
569 std::clog << "got from dpkg '" << line << "'" << std::endl;
570
571 /* dpkg sends strings like this:
572 'status: <pkg>: <pkg qstate>'
573 'status: <pkg>:<arch>: <pkg qstate>'
574
575 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: pkg'
576 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: trigger'
577 */
578
579 // we need to split on ": " (note the appended space) as the ':' is
580 // part of the pkgname:arch information that dpkg sends
581 //
582 // A dpkg error message may contain additional ":" (like
583 // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..."
584 // so we need to ensure to not split too much
585 std::vector<std::string> list = StringSplit(line, ": ", 4);
586 if(list.size() < 3)
587 {
588 if (Debug == true)
589 std::clog << "ignoring line: not enough ':'" << std::endl;
590 return;
591 }
592
593 // build the (prefix, pkgname, action) tuple, position of this
594 // is different for "processing" or "status" messages
595 std::string prefix = APT::String::Strip(list[0]);
596 std::string pkgname;
597 std::string action;
598
599 // "processing" has the form "processing: action: pkg or trigger"
600 // with action = ["install", "upgrade", "configure", "remove", "purge",
601 // "disappear", "trigproc"]
602 if (prefix == "processing")
603 {
604 pkgname = APT::String::Strip(list[2]);
605 action = APT::String::Strip(list[1]);
606 // we don't care for the difference (as dpkg doesn't really either)
607 if (action == "upgrade")
608 action = "install";
609 }
610 // "status" has the form: "status: pkg: state"
611 // with state in ["half-installed", "unpacked", "half-configured",
612 // "installed", "config-files", "not-installed"]
613 else if (prefix == "status")
614 {
615 pkgname = APT::String::Strip(list[1]);
616 action = APT::String::Strip(list[2]);
617 } else {
618 if (Debug == true)
619 std::clog << "unknown prefix '" << prefix << "'" << std::endl;
620 return;
621 }
622
623
624 /* handle the special cases first:
625
626 errors look like this:
627 '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
628 and conffile-prompt like this
629 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited
630 */
631 if (prefix == "status")
632 {
633 if(action == "error")
634 {
635 d->progress->Error(pkgname, PackagesDone, PackagesTotal,
636 list[3]);
637 pkgFailures++;
638 WriteApportReport(pkgname.c_str(), list[3].c_str());
639 return;
640 }
641 else if(action == "conffile-prompt")
642 {
643 d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal,
644 list[3]);
645 return;
646 }
647 }
648
649 // at this point we know that we should have a valid pkgname, so build all
650 // the info from it
651
652 // dpkg does not always send "pkgname:arch" so we add it here if needed
653 if (pkgname.find(":") == std::string::npos)
654 {
655 // find the package in the group that is touched by dpkg
656 // if there are multiple pkgs dpkg would send us a full pkgname:arch
657 pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname);
658 if (Grp.end() == false)
659 {
660 pkgCache::PkgIterator P = Grp.PackageList();
661 for (; P.end() != true; P = Grp.NextPkg(P))
662 {
663 if(Cache[P].Keep() == false || Cache[P].ReInstall() == true)
664 {
665 pkgname = P.FullName();
666 break;
667 }
668 }
669 }
670 }
671
672 const char* const pkg = pkgname.c_str();
673 std::string short_pkgname = StringSplit(pkgname, ":")[0];
674 std::string arch = "";
675 if (pkgname.find(":") != string::npos)
676 arch = StringSplit(pkgname, ":")[1];
677 std::string i18n_pkgname = pkgname;
678 if (arch.size() != 0)
679 strprintf(i18n_pkgname, "%s (%s)", short_pkgname.c_str(), arch.c_str());
680
681 // 'processing' from dpkg looks like
682 // 'processing: action: pkg'
683 if(prefix == "processing")
684 {
685 const std::pair<const char *, const char *> * const iter =
686 std::find_if(PackageProcessingOpsBegin,
687 PackageProcessingOpsEnd,
688 MatchProcessingOp(action.c_str()));
689 if(iter == PackageProcessingOpsEnd)
690 {
691 if (Debug == true)
692 std::clog << "ignoring unknown action: " << action << std::endl;
693 return;
694 }
695 std::string msg;
696 strprintf(msg, _(iter->second), i18n_pkgname.c_str());
697 d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
698
699 // FIXME: this needs a muliarch testcase
700 // FIXME2: is "pkgname" here reliable with dpkg only sending us
701 // short pkgnames?
702 if (action == "disappear")
703 handleDisappearAction(pkgname);
704 return;
705 }
706
707 if (prefix == "status")
708 {
709 vector<struct DpkgState> const &states = PackageOps[pkg];
710 if(PackageOpsDone[pkg] < states.size())
711 {
712 char const * const next_action = states[PackageOpsDone[pkg]].state;
713 if (next_action && Debug == true)
714 std::clog << "(parsed from dpkg) pkg: " << short_pkgname
715 << " action: " << action << " (expected: '" << next_action << "' "
716 << PackageOpsDone[pkg] << " of " << states.size() << ")" << endl;
717
718 // check if the package moved to the next dpkg state
719 if(next_action && (action == next_action))
720 {
721 // only read the translation if there is actually a next action
722 char const * const translation = _(states[PackageOpsDone[pkg]].str);
723
724 // we moved from one dpkg state to a new one, report that
725 ++PackageOpsDone[pkg];
726 ++PackagesDone;
727
728 std::string msg;
729 strprintf(msg, translation, i18n_pkgname.c_str());
730 d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
731 }
732 }
733 }
734 }
735 /*}}}*/
736 // DPkgPM::handleDisappearAction /*{{{*/
737 void pkgDPkgPM::handleDisappearAction(string const &pkgname)
738 {
739 pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
740 if (unlikely(Pkg.end() == true))
741 return;
742
743 // record the package name for display and stuff later
744 disappearedPkgs.insert(Pkg.FullName(true));
745
746 // the disappeared package was auto-installed - nothing to do
747 if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
748 return;
749 pkgCache::VerIterator PkgVer = Cache[Pkg].InstVerIter(Cache);
750 if (unlikely(PkgVer.end() == true))
751 return;
752 /* search in the list of dependencies for (Pre)Depends,
753 check if this dependency has a Replaces on our package
754 and if so transfer the manual installed flag to it */
755 for (pkgCache::DepIterator Dep = PkgVer.DependsList(); Dep.end() != true; ++Dep)
756 {
757 if (Dep->Type != pkgCache::Dep::Depends &&
758 Dep->Type != pkgCache::Dep::PreDepends)
759 continue;
760 pkgCache::PkgIterator Tar = Dep.TargetPkg();
761 if (unlikely(Tar.end() == true))
762 continue;
763 // the package is already marked as manual
764 if ((Cache[Tar].Flags & pkgCache::Flag::Auto) != pkgCache::Flag::Auto)
765 continue;
766 pkgCache::VerIterator TarVer = Cache[Tar].InstVerIter(Cache);
767 if (TarVer.end() == true)
768 continue;
769 for (pkgCache::DepIterator Rep = TarVer.DependsList(); Rep.end() != true; ++Rep)
770 {
771 if (Rep->Type != pkgCache::Dep::Replaces)
772 continue;
773 if (Pkg != Rep.TargetPkg())
774 continue;
775 // okay, they are strongly connected - transfer manual-bit
776 if (Debug == true)
777 std::clog << "transfer manual-bit from disappeared »" << pkgname << "« to »" << Tar.FullName() << "«" << std::endl;
778 Cache[Tar].Flags &= ~Flag::Auto;
779 break;
780 }
781 }
782 }
783 /*}}}*/
784 // DPkgPM::DoDpkgStatusFd /*{{{*/
785 // ---------------------------------------------------------------------
786 /*
787 */
788 void pkgDPkgPM::DoDpkgStatusFd(int statusfd)
789 {
790 char *p, *q;
791 int len;
792
793 len=read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos], sizeof(d->dpkgbuf)-d->dpkgbuf_pos);
794 d->dpkgbuf_pos += len;
795 if(len <= 0)
796 return;
797
798 // process line by line if we have a buffer
799 p = q = d->dpkgbuf;
800 while((q=(char*)memchr(p, '\n', d->dpkgbuf+d->dpkgbuf_pos-p)) != NULL)
801 {
802 *q = 0;
803 ProcessDpkgStatusLine(p);
804 p=q+1; // continue with next line
805 }
806
807 // now move the unprocessed bits (after the final \n that is now a 0x0)
808 // to the start and update d->dpkgbuf_pos
809 p = (char*)memrchr(d->dpkgbuf, 0, d->dpkgbuf_pos);
810 if(p == NULL)
811 return;
812
813 // we are interessted in the first char *after* 0x0
814 p++;
815
816 // move the unprocessed tail to the start and update pos
817 memmove(d->dpkgbuf, p, p-d->dpkgbuf);
818 d->dpkgbuf_pos = d->dpkgbuf+d->dpkgbuf_pos-p;
819 }
820 /*}}}*/
821 // DPkgPM::WriteHistoryTag /*{{{*/
822 void pkgDPkgPM::WriteHistoryTag(string const &tag, string value)
823 {
824 size_t const length = value.length();
825 if (length == 0)
826 return;
827 // poor mans rstrip(", ")
828 if (value[length-2] == ',' && value[length-1] == ' ')
829 value.erase(length - 2, 2);
830 fprintf(d->history_out, "%s: %s\n", tag.c_str(), value.c_str());
831 } /*}}}*/
832 // DPkgPM::OpenLog /*{{{*/
833 bool pkgDPkgPM::OpenLog()
834 {
835 string const logdir = _config->FindDir("Dir::Log");
836 if(CreateAPTDirectoryIfNeeded(logdir, logdir) == false)
837 // FIXME: use a better string after freeze
838 return _error->Error(_("Directory '%s' missing"), logdir.c_str());
839
840 // get current time
841 char timestr[200];
842 time_t const t = time(NULL);
843 struct tm tm_buf;
844 struct tm const * const tmp = localtime_r(&t, &tm_buf);
845 strftime(timestr, sizeof(timestr), "%F %T", tmp);
846
847 // open terminal log
848 string const logfile_name = flCombine(logdir,
849 _config->Find("Dir::Log::Terminal"));
850 if (!logfile_name.empty())
851 {
852 d->term_out = fopen(logfile_name.c_str(),"a");
853 if (d->term_out == NULL)
854 return _error->WarningE("OpenLog", _("Could not open file '%s'"), logfile_name.c_str());
855 setvbuf(d->term_out, NULL, _IONBF, 0);
856 SetCloseExec(fileno(d->term_out), true);
857 if (getuid() == 0) // if we aren't root, we can't chown a file, so don't try it
858 {
859 struct passwd *pw = getpwnam("root");
860 struct group *gr = getgrnam("adm");
861 if (pw != NULL && gr != NULL && chown(logfile_name.c_str(), pw->pw_uid, gr->gr_gid) != 0)
862 _error->WarningE("OpenLog", "chown to root:adm of file %s failed", logfile_name.c_str());
863 }
864 if (chmod(logfile_name.c_str(), 0640) != 0)
865 _error->WarningE("OpenLog", "chmod 0640 of file %s failed", logfile_name.c_str());
866 fprintf(d->term_out, "\nLog started: %s\n", timestr);
867 }
868
869 // write your history
870 string const history_name = flCombine(logdir,
871 _config->Find("Dir::Log::History"));
872 if (!history_name.empty())
873 {
874 d->history_out = fopen(history_name.c_str(),"a");
875 if (d->history_out == NULL)
876 return _error->WarningE("OpenLog", _("Could not open file '%s'"), history_name.c_str());
877 SetCloseExec(fileno(d->history_out), true);
878 chmod(history_name.c_str(), 0644);
879 fprintf(d->history_out, "\nStart-Date: %s\n", timestr);
880 string remove, purge, install, reinstall, upgrade, downgrade;
881 for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I)
882 {
883 enum { CANDIDATE, CANDIDATE_AUTO, CURRENT_CANDIDATE, CURRENT } infostring;
884 string *line = NULL;
885 #define HISTORYINFO(X, Y) { line = &X; infostring = Y; }
886 if (Cache[I].NewInstall() == true)
887 HISTORYINFO(install, CANDIDATE_AUTO)
888 else if (Cache[I].ReInstall() == true)
889 HISTORYINFO(reinstall, CANDIDATE)
890 else if (Cache[I].Upgrade() == true)
891 HISTORYINFO(upgrade, CURRENT_CANDIDATE)
892 else if (Cache[I].Downgrade() == true)
893 HISTORYINFO(downgrade, CURRENT_CANDIDATE)
894 else if (Cache[I].Delete() == true)
895 HISTORYINFO((Cache[I].Purge() ? purge : remove), CURRENT)
896 else
897 continue;
898 #undef HISTORYINFO
899 line->append(I.FullName(false)).append(" (");
900 switch (infostring) {
901 case CANDIDATE: line->append(Cache[I].CandVersion); break;
902 case CANDIDATE_AUTO:
903 line->append(Cache[I].CandVersion);
904 if ((Cache[I].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
905 line->append(", automatic");
906 break;
907 case CURRENT_CANDIDATE: line->append(Cache[I].CurVersion).append(", ").append(Cache[I].CandVersion); break;
908 case CURRENT: line->append(Cache[I].CurVersion); break;
909 }
910 line->append("), ");
911 }
912 if (_config->Exists("Commandline::AsString") == true)
913 WriteHistoryTag("Commandline", _config->Find("Commandline::AsString"));
914 WriteHistoryTag("Install", install);
915 WriteHistoryTag("Reinstall", reinstall);
916 WriteHistoryTag("Upgrade", upgrade);
917 WriteHistoryTag("Downgrade",downgrade);
918 WriteHistoryTag("Remove",remove);
919 WriteHistoryTag("Purge",purge);
920 fflush(d->history_out);
921 }
922
923 return true;
924 }
925 /*}}}*/
926 // DPkg::CloseLog /*{{{*/
927 bool pkgDPkgPM::CloseLog()
928 {
929 char timestr[200];
930 time_t t = time(NULL);
931 struct tm tm_buf;
932 struct tm *tmp = localtime_r(&t, &tm_buf);
933 strftime(timestr, sizeof(timestr), "%F %T", tmp);
934
935 if(d->term_out)
936 {
937 fprintf(d->term_out, "Log ended: ");
938 fprintf(d->term_out, "%s", timestr);
939 fprintf(d->term_out, "\n");
940 fclose(d->term_out);
941 }
942 d->term_out = NULL;
943
944 if(d->history_out)
945 {
946 if (disappearedPkgs.empty() == false)
947 {
948 string disappear;
949 for (std::set<std::string>::const_iterator d = disappearedPkgs.begin();
950 d != disappearedPkgs.end(); ++d)
951 {
952 pkgCache::PkgIterator P = Cache.FindPkg(*d);
953 disappear.append(*d);
954 if (P.end() == true)
955 disappear.append(", ");
956 else
957 disappear.append(" (").append(Cache[P].CurVersion).append("), ");
958 }
959 WriteHistoryTag("Disappeared", disappear);
960 }
961 if (d->dpkg_error.empty() == false)
962 fprintf(d->history_out, "Error: %s\n", d->dpkg_error.c_str());
963 fprintf(d->history_out, "End-Date: %s\n", timestr);
964 fclose(d->history_out);
965 }
966 d->history_out = NULL;
967
968 return true;
969 }
970 /*}}}*/
971 /*}}}*/
972 /*{{{*/
973 // This implements a racy version of pselect for those architectures
974 // that don't have a working implementation.
975 // FIXME: Probably can be removed on Lenny+1
976 static int racy_pselect(int nfds, fd_set *readfds, fd_set *writefds,
977 fd_set *exceptfds, const struct timespec *timeout,
978 const sigset_t *sigmask)
979 {
980 sigset_t origmask;
981 struct timeval tv;
982 int retval;
983
984 tv.tv_sec = timeout->tv_sec;
985 tv.tv_usec = timeout->tv_nsec/1000;
986
987 sigprocmask(SIG_SETMASK, sigmask, &origmask);
988 retval = select(nfds, readfds, writefds, exceptfds, &tv);
989 sigprocmask(SIG_SETMASK, &origmask, 0);
990 return retval;
991 }
992 /*}}}*/
993
994 // DPkgPM::BuildPackagesProgressMap /*{{{*/
995 void pkgDPkgPM::BuildPackagesProgressMap()
996 {
997 // map the dpkg states to the operations that are performed
998 // (this is sorted in the same way as Item::Ops)
999 static const struct DpkgState DpkgStatesOpMap[][7] = {
1000 // Install operation
1001 {
1002 {"half-installed", N_("Preparing %s")},
1003 {"unpacked", N_("Unpacking %s") },
1004 {NULL, NULL}
1005 },
1006 // Configure operation
1007 {
1008 {"unpacked",N_("Preparing to configure %s") },
1009 {"half-configured", N_("Configuring %s") },
1010 { "installed", N_("Installed %s")},
1011 {NULL, NULL}
1012 },
1013 // Remove operation
1014 {
1015 {"half-configured", N_("Preparing for removal of %s")},
1016 {"half-installed", N_("Removing %s")},
1017 {"config-files", N_("Removed %s")},
1018 {NULL, NULL}
1019 },
1020 // Purge operation
1021 {
1022 {"config-files", N_("Preparing to completely remove %s")},
1023 {"not-installed", N_("Completely removed %s")},
1024 {NULL, NULL}
1025 },
1026 };
1027
1028 // init the PackageOps map, go over the list of packages that
1029 // that will be [installed|configured|removed|purged] and add
1030 // them to the PackageOps map (the dpkg states it goes through)
1031 // and the PackageOpsTranslations (human readable strings)
1032 for (vector<Item>::const_iterator I = List.begin(); I != List.end(); ++I)
1033 {
1034 if((*I).Pkg.end() == true)
1035 continue;
1036
1037 string const name = (*I).Pkg.FullName();
1038 PackageOpsDone[name] = 0;
1039 for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i)
1040 {
1041 PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]);
1042 PackagesTotal++;
1043 }
1044 }
1045 /* one extra: We don't want the progress bar to reach 100%, especially not
1046 if we call dpkg --configure --pending and process a bunch of triggers
1047 while showing 100%. Also, spindown takes a while, so never reaching 100%
1048 is way more correct than reaching 100% while still doing stuff even if
1049 doing it this way is slightly bending the rules */
1050 ++PackagesTotal;
1051 }
1052 /*}}}*/
1053 bool pkgDPkgPM::Go(int StatusFd)
1054 {
1055 APT::Progress::PackageManager *progress = NULL;
1056 if (StatusFd == -1)
1057 progress = APT::Progress::PackageManagerProgressFactory();
1058 else
1059 progress = new APT::Progress::PackageManagerProgressFd(StatusFd);
1060
1061 return Go(progress);
1062 }
1063
1064 void pkgDPkgPM::StartPtyMagic()
1065 {
1066 if (_config->FindB("Dpkg::Use-Pty", true) == false)
1067 {
1068 d->master = -1;
1069 if (d->slave != NULL)
1070 free(d->slave);
1071 d->slave = NULL;
1072 return;
1073 }
1074
1075 if (isatty(STDIN_FILENO) == 0)
1076 d->direct_stdin = true;
1077
1078 _error->PushToStack();
1079
1080 d->master = posix_openpt(O_RDWR | O_NOCTTY);
1081 if (d->master == -1)
1082 _error->Errno("posix_openpt", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
1083 else if (unlockpt(d->master) == -1)
1084 _error->Errno("unlockpt", "Unlocking the slave of master fd %d failed!", d->master);
1085 else
1086 {
1087 #ifdef HAVE_PTS_NAME_R
1088 char slave_name[64]; // 64 is used by bionic
1089 if (ptsname_r(d->master, slave_name, sizeof(slave_name)) != 0)
1090 #else
1091 char const * const slave_name = ptsname(d->master);
1092 if (slave_name == NULL)
1093 #endif
1094 _error->Errno("ptsname", "Getting name for slave of master fd %d failed!", d->master);
1095 else
1096 {
1097 d->slave = strdup(slave_name);
1098 if (d->slave == NULL)
1099 _error->Errno("strdup", "Copying name %s for slave of master fd %d failed!", slave_name, d->master);
1100 else if (grantpt(d->master) == -1)
1101 _error->Errno("grantpt", "Granting access to slave %s based on master fd %d failed!", slave_name, d->master);
1102 else if (tcgetattr(STDIN_FILENO, &d->tt) == 0)
1103 {
1104 d->tt_is_valid = true;
1105 struct termios raw_tt;
1106 // copy window size of stdout if its a 'good' terminal
1107 if (tcgetattr(STDOUT_FILENO, &raw_tt) == 0)
1108 {
1109 struct winsize win;
1110 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0)
1111 _error->Errno("ioctl", "Getting TIOCGWINSZ from stdout failed!");
1112 if (ioctl(d->master, TIOCSWINSZ, &win) < 0)
1113 _error->Errno("ioctl", "Setting TIOCSWINSZ for master fd %d failed!", d->master);
1114 }
1115 if (tcsetattr(d->master, TCSANOW, &d->tt) == -1)
1116 _error->Errno("tcsetattr", "Setting in Start via TCSANOW for master fd %d failed!", d->master);
1117
1118 raw_tt = d->tt;
1119 cfmakeraw(&raw_tt);
1120 raw_tt.c_lflag &= ~ECHO;
1121 raw_tt.c_lflag |= ISIG;
1122 // block SIGTTOU during tcsetattr to prevent a hang if
1123 // the process is a member of the background process group
1124 // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html
1125 sigemptyset(&d->sigmask);
1126 sigaddset(&d->sigmask, SIGTTOU);
1127 sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask);
1128 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_tt) == -1)
1129 _error->Errno("tcsetattr", "Setting in Start via TCSAFLUSH for stdin failed!");
1130 sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL);
1131
1132 }
1133 if (d->slave != NULL)
1134 {
1135 /* on linux, closing (and later reopening) all references to the slave
1136 makes the slave a death end, so we open it here to have one open all
1137 the time. We could use this fd in SetupSlavePtyMagic() for linux, but
1138 on kfreebsd we get an incorrect ("step like") output then while it has
1139 no problem with closing all references… so to avoid platform specific
1140 code here we combine both and be happy once more */
1141 d->protect_slave_from_dying = open(d->slave, O_RDWR | O_CLOEXEC | O_NOCTTY);
1142 }
1143 }
1144 }
1145
1146 if (_error->PendingError() == true)
1147 {
1148 if (d->master != -1)
1149 {
1150 close(d->master);
1151 d->master = -1;
1152 }
1153 if (d->slave != NULL)
1154 {
1155 free(d->slave);
1156 d->slave = NULL;
1157 }
1158 _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
1159 }
1160 _error->RevertToStack();
1161 }
1162 void pkgDPkgPM::SetupSlavePtyMagic()
1163 {
1164 if(d->master == -1 || d->slave == NULL)
1165 return;
1166
1167 if (close(d->master) == -1)
1168 _error->FatalE("close", "Closing master %d in child failed!", d->master);
1169 d->master = -1;
1170 if (setsid() == -1)
1171 _error->FatalE("setsid", "Starting a new session for child failed!");
1172
1173 int const slaveFd = open(d->slave, O_RDWR | O_NOCTTY);
1174 if (slaveFd == -1)
1175 _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
1176 else if (ioctl(slaveFd, TIOCSCTTY, 0) < 0)
1177 _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd);
1178 else
1179 {
1180 unsigned short i = 0;
1181 if (d->direct_stdin == true)
1182 ++i;
1183 for (; i < 3; ++i)
1184 if (dup2(slaveFd, i) == -1)
1185 _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i);
1186
1187 if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSANOW, &d->tt) < 0)
1188 _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd);
1189 }
1190
1191 if (slaveFd != -1)
1192 close(slaveFd);
1193 }
1194 void pkgDPkgPM::StopPtyMagic()
1195 {
1196 if (d->slave != NULL)
1197 free(d->slave);
1198 d->slave = NULL;
1199 if (d->protect_slave_from_dying != -1)
1200 {
1201 close(d->protect_slave_from_dying);
1202 d->protect_slave_from_dying = -1;
1203 }
1204 if(d->master >= 0)
1205 {
1206 if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSAFLUSH, &d->tt) == -1)
1207 _error->FatalE("tcsetattr", "Setting in Stop via TCSAFLUSH for stdin failed!");
1208 close(d->master);
1209 d->master = -1;
1210 }
1211 }
1212
1213 // DPkgPM::Go - Run the sequence /*{{{*/
1214 // ---------------------------------------------------------------------
1215 /* This globs the operations and calls dpkg
1216 *
1217 * If it is called with a progress object apt will report the install
1218 * progress to this object. It maps the dpkg states a package goes
1219 * through to human readable (and i10n-able)
1220 * names and calculates a percentage for each step.
1221 */
1222 bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
1223 {
1224 pkgPackageManager::SigINTStop = false;
1225 d->progress = progress;
1226
1227 // Generate the base argument list for dpkg
1228 unsigned long StartSize = 0;
1229 std::vector<const char *> Args;
1230 std::string DpkgExecutable = getDpkgExecutable();
1231 Args.push_back(DpkgExecutable.c_str());
1232 StartSize += DpkgExecutable.length();
1233
1234 // Stick in any custom dpkg options
1235 Configuration::Item const *Opts = _config->Tree("DPkg::Options");
1236 if (Opts != 0)
1237 {
1238 Opts = Opts->Child;
1239 for (; Opts != 0; Opts = Opts->Next)
1240 {
1241 if (Opts->Value.empty() == true)
1242 continue;
1243 Args.push_back(Opts->Value.c_str());
1244 StartSize += Opts->Value.length();
1245 }
1246 }
1247
1248 size_t const BaseArgs = Args.size();
1249 // we need to detect if we can qualify packages with the architecture or not
1250 Args.push_back("--assert-multi-arch");
1251 Args.push_back(NULL);
1252
1253 pid_t dpkgAssertMultiArch = ExecFork();
1254 if (dpkgAssertMultiArch == 0)
1255 {
1256 dpkgChrootDirectory();
1257 // redirect everything to the ultimate sink as we only need the exit-status
1258 int const nullfd = open("/dev/null", O_RDONLY);
1259 dup2(nullfd, STDIN_FILENO);
1260 dup2(nullfd, STDOUT_FILENO);
1261 dup2(nullfd, STDERR_FILENO);
1262 execvp(Args[0], (char**) &Args[0]);
1263 _error->WarningE("dpkgGo", "Can't detect if dpkg supports multi-arch!");
1264 _exit(2);
1265 }
1266
1267 fd_set rfds;
1268 struct timespec tv;
1269
1270 // FIXME: do we really need this limit when we have MaxArgBytes?
1271 unsigned int const MaxArgs = _config->FindI("Dpkg::MaxArgs",32*1024);
1272
1273 // try to figure out the max environment size
1274 int OSArgMax = sysconf(_SC_ARG_MAX);
1275 if(OSArgMax < 0)
1276 OSArgMax = 32*1024;
1277 OSArgMax -= EnvironmentSize() - 2*1024;
1278 unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax);
1279 bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false);
1280
1281 if (RunScripts("DPkg::Pre-Invoke") == false)
1282 return false;
1283
1284 if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
1285 return false;
1286
1287 // support subpressing of triggers processing for special
1288 // cases like d-i that runs the triggers handling manually
1289 bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
1290 if (_config->FindB("DPkg::ConfigurePending", true) == true)
1291 List.push_back(Item(Item::ConfigurePending, PkgIterator()));
1292
1293 // for the progress
1294 BuildPackagesProgressMap();
1295
1296 d->stdin_is_dev_null = false;
1297
1298 // create log
1299 OpenLog();
1300
1301 bool dpkgMultiArch = false;
1302 if (dpkgAssertMultiArch > 0)
1303 {
1304 int Status = 0;
1305 while (waitpid(dpkgAssertMultiArch, &Status, 0) != dpkgAssertMultiArch)
1306 {
1307 if (errno == EINTR)
1308 continue;
1309 _error->WarningE("dpkgGo", _("Waited for %s but it wasn't there"), "dpkg --assert-multi-arch");
1310 break;
1311 }
1312 if (WIFEXITED(Status) == true && WEXITSTATUS(Status) == 0)
1313 dpkgMultiArch = true;
1314 }
1315
1316 // start pty magic before the loop
1317 StartPtyMagic();
1318
1319 // Tell the progress that its starting and fork dpkg
1320 d->progress->Start(d->master);
1321
1322 // this loop is runs once per dpkg operation
1323 vector<Item>::const_iterator I = List.begin();
1324 while (I != List.end())
1325 {
1326 // Do all actions with the same Op in one run
1327 vector<Item>::const_iterator J = I;
1328 if (TriggersPending == true)
1329 for (; J != List.end(); ++J)
1330 {
1331 if (J->Op == I->Op)
1332 continue;
1333 if (J->Op != Item::TriggersPending)
1334 break;
1335 vector<Item>::const_iterator T = J + 1;
1336 if (T != List.end() && T->Op == I->Op)
1337 continue;
1338 break;
1339 }
1340 else
1341 for (; J != List.end() && J->Op == I->Op; ++J)
1342 /* nothing */;
1343
1344 // keep track of allocated strings for multiarch package names
1345 std::vector<char *> Packages;
1346
1347 // start with the baseset of arguments
1348 unsigned long Size = StartSize;
1349 Args.erase(Args.begin() + BaseArgs, Args.end());
1350
1351 // Now check if we are within the MaxArgs limit
1352 //
1353 // this code below is problematic, because it may happen that
1354 // the argument list is split in a way that A depends on B
1355 // and they are in the same "--configure A B" run
1356 // - with the split they may now be configured in different
1357 // runs, using Immediate-Configure-All can help prevent this.
1358 if (J - I > (signed)MaxArgs)
1359 {
1360 J = I + MaxArgs;
1361 unsigned long const size = MaxArgs + 10;
1362 Args.reserve(size);
1363 Packages.reserve(size);
1364 }
1365 else
1366 {
1367 unsigned long const size = (J - I) + 10;
1368 Args.reserve(size);
1369 Packages.reserve(size);
1370 }
1371
1372 int fd[2];
1373 if (pipe(fd) != 0)
1374 return _error->Errno("pipe","Failed to create IPC pipe to dpkg");
1375
1376 #define ADDARG(X) Args.push_back(X); Size += strlen(X)
1377 #define ADDARGC(X) Args.push_back(X); Size += sizeof(X) - 1
1378
1379 ADDARGC("--status-fd");
1380 char status_fd_buf[20];
1381 snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]);
1382 ADDARG(status_fd_buf);
1383 unsigned long const Op = I->Op;
1384
1385 switch (I->Op)
1386 {
1387 case Item::Remove:
1388 ADDARGC("--force-depends");
1389 ADDARGC("--force-remove-essential");
1390 ADDARGC("--remove");
1391 break;
1392
1393 case Item::Purge:
1394 ADDARGC("--force-depends");
1395 ADDARGC("--force-remove-essential");
1396 ADDARGC("--purge");
1397 break;
1398
1399 case Item::Configure:
1400 ADDARGC("--configure");
1401 break;
1402
1403 case Item::ConfigurePending:
1404 ADDARGC("--configure");
1405 ADDARGC("--pending");
1406 break;
1407
1408 case Item::TriggersPending:
1409 ADDARGC("--triggers-only");
1410 ADDARGC("--pending");
1411 break;
1412
1413 case Item::Install:
1414 ADDARGC("--unpack");
1415 ADDARGC("--auto-deconfigure");
1416 break;
1417 }
1418
1419 if (NoTriggers == true && I->Op != Item::TriggersPending &&
1420 I->Op != Item::ConfigurePending)
1421 {
1422 ADDARGC("--no-triggers");
1423 }
1424 #undef ADDARGC
1425
1426 // Write in the file or package names
1427 if (I->Op == Item::Install)
1428 {
1429 for (;I != J && Size < MaxArgBytes; ++I)
1430 {
1431 if (I->File[0] != '/')
1432 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
1433 Args.push_back(I->File.c_str());
1434 Size += I->File.length();
1435 }
1436 }
1437 else
1438 {
1439 string const nativeArch = _config->Find("APT::Architecture");
1440 unsigned long const oldSize = I->Op == Item::Configure ? Size : 0;
1441 for (;I != J && Size < MaxArgBytes; ++I)
1442 {
1443 if((*I).Pkg.end() == true)
1444 continue;
1445 if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.FullName(true)) != disappearedPkgs.end())
1446 continue;
1447 // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian
1448 if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch ||
1449 strcmp(I->Pkg.Arch(), "all") == 0 ||
1450 strcmp(I->Pkg.Arch(), "none") == 0))
1451 {
1452 char const * const name = I->Pkg.Name();
1453 ADDARG(name);
1454 }
1455 else
1456 {
1457 pkgCache::VerIterator PkgVer;
1458 std::string name = I->Pkg.Name();
1459 if (Op == Item::Remove || Op == Item::Purge)
1460 {
1461 PkgVer = I->Pkg.CurrentVer();
1462 if(PkgVer.end() == true)
1463 PkgVer = FindNowVersion(I->Pkg);
1464 }
1465 else
1466 PkgVer = Cache[I->Pkg].InstVerIter(Cache);
1467 if (strcmp(I->Pkg.Arch(), "none") == 0)
1468 ; // never arch-qualify a package without an arch
1469 else if (PkgVer.end() == false)
1470 name.append(":").append(PkgVer.Arch());
1471 else
1472 _error->Warning("Can not find PkgVer for '%s'", name.c_str());
1473 char * const fullname = strdup(name.c_str());
1474 Packages.push_back(fullname);
1475 ADDARG(fullname);
1476 }
1477 }
1478 // skip configure action if all sheduled packages disappeared
1479 if (oldSize == Size)
1480 continue;
1481 }
1482 #undef ADDARG
1483
1484 J = I;
1485
1486 if (_config->FindB("Debug::pkgDPkgPM",false) == true)
1487 {
1488 for (std::vector<const char *>::const_iterator a = Args.begin();
1489 a != Args.end(); ++a)
1490 clog << *a << ' ';
1491 clog << endl;
1492 for (std::vector<char *>::const_iterator p = Packages.begin();
1493 p != Packages.end(); ++p)
1494 free(*p);
1495 Packages.clear();
1496 continue;
1497 }
1498 Args.push_back(NULL);
1499
1500 cout << flush;
1501 clog << flush;
1502 cerr << flush;
1503
1504 /* Mask off sig int/quit. We do this because dpkg also does when
1505 it forks scripts. What happens is that when you hit ctrl-c it sends
1506 it to all processes in the group. Since dpkg ignores the signal
1507 it doesn't die but we do! So we must also ignore it */
1508 sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
1509 sighandler_t old_SIGINT = signal(SIGINT,SigINT);
1510
1511 // Check here for any SIGINT
1512 if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install))
1513 break;
1514
1515
1516 // ignore SIGHUP as well (debian #463030)
1517 sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN);
1518
1519 // now run dpkg
1520 d->progress->StartDpkg();
1521 std::set<int> KeepFDs;
1522 KeepFDs.insert(fd[1]);
1523 MergeKeepFdsFromConfiguration(KeepFDs);
1524 pid_t Child = ExecFork(KeepFDs);
1525 if (Child == 0)
1526 {
1527 // This is the child
1528 SetupSlavePtyMagic();
1529 close(fd[0]); // close the read end of the pipe
1530
1531 dpkgChrootDirectory();
1532
1533 if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
1534 _exit(100);
1535
1536 if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
1537 {
1538 int Flags;
1539 int dummy = 0;
1540 if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
1541 _exit(100);
1542
1543 // Discard everything in stdin before forking dpkg
1544 if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
1545 _exit(100);
1546
1547 while (read(STDIN_FILENO,&dummy,1) == 1);
1548
1549 if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
1550 _exit(100);
1551 }
1552
1553 execvp(Args[0], (char**) &Args[0]);
1554 cerr << "Could not exec dpkg!" << endl;
1555 _exit(100);
1556 }
1557
1558 // apply ionice
1559 if (_config->FindB("DPkg::UseIoNice", false) == true)
1560 ionice(Child);
1561
1562 // Wait for dpkg
1563 int Status = 0;
1564
1565 // we read from dpkg here
1566 int const _dpkgin = fd[0];
1567 close(fd[1]); // close the write end of the pipe
1568
1569 // setups fds
1570 sigemptyset(&d->sigmask);
1571 sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask);
1572
1573 /* free vectors (and therefore memory) as we don't need the included data anymore */
1574 for (std::vector<char *>::const_iterator p = Packages.begin();
1575 p != Packages.end(); ++p)
1576 free(*p);
1577 Packages.clear();
1578
1579 // the result of the waitpid call
1580 int res;
1581 int select_ret;
1582 while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
1583 if(res < 0) {
1584 // FIXME: move this to a function or something, looks ugly here
1585 // error handling, waitpid returned -1
1586 if (errno == EINTR)
1587 continue;
1588 RunScripts("DPkg::Post-Invoke");
1589
1590 // Restore sig int/quit
1591 signal(SIGQUIT,old_SIGQUIT);
1592 signal(SIGINT,old_SIGINT);
1593
1594 signal(SIGHUP,old_SIGHUP);
1595 return _error->Errno("waitpid","Couldn't wait for subprocess");
1596 }
1597
1598 // wait for input or output here
1599 FD_ZERO(&rfds);
1600 if (d->master >= 0 && d->direct_stdin == false && d->stdin_is_dev_null == false)
1601 FD_SET(STDIN_FILENO, &rfds);
1602 FD_SET(_dpkgin, &rfds);
1603 if(d->master >= 0)
1604 FD_SET(d->master, &rfds);
1605 tv.tv_sec = 0;
1606 tv.tv_nsec = d->progress->GetPulseInterval();
1607 select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL,
1608 &tv, &d->original_sigmask);
1609 if (select_ret < 0 && (errno == EINVAL || errno == ENOSYS))
1610 select_ret = racy_pselect(max(d->master, _dpkgin)+1, &rfds, NULL,
1611 NULL, &tv, &d->original_sigmask);
1612 d->progress->Pulse();
1613 if (select_ret == 0)
1614 continue;
1615 else if (select_ret < 0 && errno == EINTR)
1616 continue;
1617 else if (select_ret < 0)
1618 {
1619 perror("select() returned error");
1620 continue;
1621 }
1622
1623 if(d->master >= 0 && FD_ISSET(d->master, &rfds))
1624 DoTerminalPty(d->master);
1625 if(d->master >= 0 && FD_ISSET(0, &rfds))
1626 DoStdin(d->master);
1627 if(FD_ISSET(_dpkgin, &rfds))
1628 DoDpkgStatusFd(_dpkgin);
1629 }
1630 close(_dpkgin);
1631
1632 // Restore sig int/quit
1633 signal(SIGQUIT,old_SIGQUIT);
1634 signal(SIGINT,old_SIGINT);
1635
1636 signal(SIGHUP,old_SIGHUP);
1637 // Check for an error code.
1638 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
1639 {
1640 // if it was set to "keep-dpkg-runing" then we won't return
1641 // here but keep the loop going and just report it as a error
1642 // for later
1643 bool const stopOnError = _config->FindB("Dpkg::StopOnError",true);
1644
1645 if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
1646 strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]);
1647 else if (WIFEXITED(Status) != 0)
1648 strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status));
1649 else
1650 strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]);
1651 _error->Error("%s", d->dpkg_error.c_str());
1652
1653 if(stopOnError)
1654 break;
1655 }
1656 }
1657 // dpkg is done at this point
1658 d->progress->Stop();
1659 StopPtyMagic();
1660 CloseLog();
1661
1662 if (pkgPackageManager::SigINTStop)
1663 _error->Warning(_("Operation was interrupted before it could finish"));
1664
1665 if (RunScripts("DPkg::Post-Invoke") == false)
1666 return false;
1667
1668 if (_config->FindB("Debug::pkgDPkgPM",false) == false)
1669 {
1670 std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache");
1671 if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true &&
1672 unlink(oldpkgcache.c_str()) == 0)
1673 {
1674 std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache");
1675 if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true)
1676 {
1677 _error->PushToStack();
1678 pkgCacheFile CacheFile;
1679 CacheFile.BuildCaches(NULL, true);
1680 _error->RevertToStack();
1681 }
1682 }
1683 }
1684
1685 Cache.writeStateFile(NULL);
1686 return d->dpkg_error.empty();
1687 }
1688
1689 void SigINT(int /*sig*/) {
1690 pkgPackageManager::SigINTStop = true;
1691 }
1692 /*}}}*/
1693 // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/
1694 // ---------------------------------------------------------------------
1695 /* */
1696 void pkgDPkgPM::Reset()
1697 {
1698 List.erase(List.begin(),List.end());
1699 }
1700 /*}}}*/
1701 // pkgDpkgPM::WriteApportReport - write out error report pkg failure /*{{{*/
1702 // ---------------------------------------------------------------------
1703 /* */
1704 void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
1705 {
1706 // If apport doesn't exist or isn't installed do nothing
1707 // This e.g. prevents messages in 'universes' without apport
1708 pkgCache::PkgIterator apportPkg = Cache.FindPkg("apport");
1709 if (apportPkg.end() == true || apportPkg->CurrentVer == 0)
1710 return;
1711
1712 string pkgname, reportfile, pkgver, arch;
1713 string::size_type pos;
1714 FILE *report;
1715
1716 if (_config->FindB("Dpkg::ApportFailureReport", true) == false)
1717 {
1718 std::clog << "configured to not write apport reports" << std::endl;
1719 return;
1720 }
1721
1722 // only report the first errors
1723 if(pkgFailures > _config->FindI("APT::Apport::MaxReports", 3))
1724 {
1725 std::clog << _("No apport report written because MaxReports is reached already") << std::endl;
1726 return;
1727 }
1728
1729 // check if its not a follow up error
1730 const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured");
1731 if(strstr(errormsg, needle) != NULL) {
1732 std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl;
1733 return;
1734 }
1735
1736 // do not report disk-full failures
1737 if(strstr(errormsg, strerror(ENOSPC)) != NULL) {
1738 std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl;
1739 return;
1740 }
1741
1742 // do not report out-of-memory failures
1743 if(strstr(errormsg, strerror(ENOMEM)) != NULL ||
1744 strstr(errormsg, "failed to allocate memory") != NULL) {
1745 std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl;
1746 return;
1747 }
1748
1749 // do not report bugs regarding inaccessible local files
1750 if(strstr(errormsg, strerror(ENOENT)) != NULL ||
1751 strstr(errormsg, "cannot access archive") != NULL) {
1752 std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
1753 return;
1754 }
1755
1756 // do not report errors encountered when decompressing packages
1757 if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) {
1758 std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
1759 return;
1760 }
1761
1762 // do not report dpkg I/O errors, this is a format string, so we compare
1763 // the prefix and the suffix of the error with the dpkg error message
1764 vector<string> io_errors;
1765 io_errors.push_back(string("failed to read"));
1766 io_errors.push_back(string("failed to write"));
1767 io_errors.push_back(string("failed to seek"));
1768 io_errors.push_back(string("unexpected end of file or stream"));
1769
1770 for (vector<string>::iterator I = io_errors.begin(); I != io_errors.end(); ++I)
1771 {
1772 vector<string> list = VectorizeString(dgettext("dpkg", (*I).c_str()), '%');
1773 if (list.size() > 1) {
1774 // we need to split %s, VectorizeString only allows char so we need
1775 // to kill the "s" manually
1776 if (list[1].size() > 1) {
1777 list[1].erase(0, 1);
1778 if(strstr(errormsg, list[0].c_str()) &&
1779 strstr(errormsg, list[1].c_str())) {
1780 std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl;
1781 return;
1782 }
1783 }
1784 }
1785 }
1786
1787 // get the pkgname and reportfile
1788 pkgname = flNotDir(pkgpath);
1789 pos = pkgname.find('_');
1790 if(pos != string::npos)
1791 pkgname = pkgname.substr(0, pos);
1792
1793 // find the package versin and source package name
1794 pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
1795 if (Pkg.end() == true)
1796 return;
1797 pkgCache::VerIterator Ver = Cache.GetCandidateVer(Pkg);
1798 if (Ver.end() == true)
1799 return;
1800 pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr();
1801
1802 // if the file exists already, we check:
1803 // - if it was reported already (touched by apport).
1804 // If not, we do nothing, otherwise
1805 // we overwrite it. This is the same behaviour as apport
1806 // - if we have a report with the same pkgversion already
1807 // then we skip it
1808 reportfile = flCombine("/var/crash",pkgname+".0.crash");
1809 if(FileExists(reportfile))
1810 {
1811 struct stat buf;
1812 char strbuf[255];
1813
1814 // check atime/mtime
1815 stat(reportfile.c_str(), &buf);
1816 if(buf.st_mtime > buf.st_atime)
1817 return;
1818
1819 // check if the existing report is the same version
1820 report = fopen(reportfile.c_str(),"r");
1821 while(fgets(strbuf, sizeof(strbuf), report) != NULL)
1822 {
1823 if(strstr(strbuf,"Package:") == strbuf)
1824 {
1825 char pkgname[255], version[255];
1826 if(sscanf(strbuf, "Package: %254s %254s", pkgname, version) == 2)
1827 if(strcmp(pkgver.c_str(), version) == 0)
1828 {
1829 fclose(report);
1830 return;
1831 }
1832 }
1833 }
1834 fclose(report);
1835 }
1836
1837 // now write the report
1838 arch = _config->Find("APT::Architecture");
1839 report = fopen(reportfile.c_str(),"w");
1840 if(report == NULL)
1841 return;
1842 if(_config->FindB("DPkgPM::InitialReportOnly",false) == true)
1843 chmod(reportfile.c_str(), 0);
1844 else
1845 chmod(reportfile.c_str(), 0600);
1846 fprintf(report, "ProblemType: Package\n");
1847 fprintf(report, "Architecture: %s\n", arch.c_str());
1848 time_t now = time(NULL);
1849 char ctime_buf[26]; // need at least 26 bytes according to ctime(3)
1850 fprintf(report, "Date: %s" , ctime_r(&now, ctime_buf));
1851 fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str());
1852 fprintf(report, "SourcePackage: %s\n", Ver.SourcePkgName());
1853 fprintf(report, "ErrorMessage:\n %s\n", errormsg);
1854
1855 // ensure that the log is flushed
1856 if(d->term_out)
1857 fflush(d->term_out);
1858
1859 // attach terminal log it if we have it
1860 string logfile_name = _config->FindFile("Dir::Log::Terminal");
1861 if (!logfile_name.empty())
1862 {
1863 FILE *log = NULL;
1864
1865 fprintf(report, "DpkgTerminalLog:\n");
1866 log = fopen(logfile_name.c_str(),"r");
1867 if(log != NULL)
1868 {
1869 char buf[1024];
1870 while( fgets(buf, sizeof(buf), log) != NULL)
1871 fprintf(report, " %s", buf);
1872 fprintf(report, " \n");
1873 fclose(log);
1874 }
1875 }
1876
1877 // attach history log it if we have it
1878 string histfile_name = _config->FindFile("Dir::Log::History");
1879 if (!histfile_name.empty())
1880 {
1881 fprintf(report, "DpkgHistoryLog:\n");
1882 FILE* log = fopen(histfile_name.c_str(),"r");
1883 if(log != NULL)
1884 {
1885 char buf[1024];
1886 while( fgets(buf, sizeof(buf), log) != NULL)
1887 fprintf(report, " %s", buf);
1888 fclose(log);
1889 }
1890 }
1891
1892 // log the ordering, see dpkgpm.h and the "Ops" enum there
1893 const char *ops_str[] = {
1894 "Install",
1895 "Configure",
1896 "Remove",
1897 "Purge",
1898 "ConfigurePending",
1899 "TriggersPending",
1900 };
1901 fprintf(report, "AptOrdering:\n");
1902 for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
1903 if ((*I).Pkg != NULL)
1904 fprintf(report, " %s: %s\n", (*I).Pkg.Name(), ops_str[(*I).Op]);
1905 else
1906 fprintf(report, " %s: %s\n", "NULL", ops_str[(*I).Op]);
1907
1908 // attach dmesg log (to learn about segfaults)
1909 if (FileExists("/bin/dmesg"))
1910 {
1911 fprintf(report, "Dmesg:\n");
1912 FILE *log = popen("/bin/dmesg","r");
1913 if(log != NULL)
1914 {
1915 char buf[1024];
1916 while( fgets(buf, sizeof(buf), log) != NULL)
1917 fprintf(report, " %s", buf);
1918 pclose(log);
1919 }
1920 }
1921
1922 // attach df -l log (to learn about filesystem status)
1923 if (FileExists("/bin/df"))
1924 {
1925
1926 fprintf(report, "Df:\n");
1927 FILE *log = popen("/bin/df -l","r");
1928 if(log != NULL)
1929 {
1930 char buf[1024];
1931 while( fgets(buf, sizeof(buf), log) != NULL)
1932 fprintf(report, " %s", buf);
1933 pclose(log);
1934 }
1935 }
1936
1937 fclose(report);
1938
1939 }
1940 /*}}}*/