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