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