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