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