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