1 // -*- mode: cpp; mode: fold -*-
3 // $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $
4 /* ######################################################################
6 DPKG Package Manager - Provide an interface to dpkg
8 ##################################################################### */
13 #include <apt-pkg/dpkgpm.h>
14 #include <apt-pkg/error.h>
15 #include <apt-pkg/configuration.h>
16 #include <apt-pkg/depcache.h>
17 #include <apt-pkg/pkgrecords.h>
18 #include <apt-pkg/strutl.h>
19 #include <apt-pkg/fileutl.h>
20 #include <apt-pkg/cachefile.h>
21 #include <apt-pkg/packagemanager.h>
26 #include <sys/select.h>
28 #include <sys/types.h>
42 #include <sys/ioctl.h>
50 class pkgDPkgPMPrivate
53 pkgDPkgPMPrivate() : dpkgbuf_pos(0), term_out(NULL
), history_out(NULL
)
56 bool stdin_is_dev_null
;
57 // the buffer we use for the dpkg status-fd reading
67 // Maps the dpkg "processing" info to human readable names. Entry 0
68 // of each array is the key, entry 1 is the value.
69 const std::pair
<const char *, const char *> PackageProcessingOps
[] = {
70 std::make_pair("install", N_("Installing %s")),
71 std::make_pair("configure", N_("Configuring %s")),
72 std::make_pair("remove", N_("Removing %s")),
73 std::make_pair("purge", N_("Completely removing %s")),
74 std::make_pair("disappear", N_("Noting disappearance of %s")),
75 std::make_pair("trigproc", N_("Running post-installation trigger %s"))
78 const std::pair
<const char *, const char *> * const PackageProcessingOpsBegin
= PackageProcessingOps
;
79 const std::pair
<const char *, const char *> * const PackageProcessingOpsEnd
= PackageProcessingOps
+ sizeof(PackageProcessingOps
) / sizeof(PackageProcessingOps
[0]);
81 // Predicate to test whether an entry in the PackageProcessingOps
82 // array matches a string.
83 class MatchProcessingOp
88 MatchProcessingOp(const char *the_target
)
93 bool operator()(const std::pair
<const char *, const char *> &pair
) const
95 return strcmp(pair
.first
, target
) == 0;
100 /* helper function to ionice the given PID
102 there is no C header for ionice yet - just the syscall interface
103 so we use the binary from util-linux
108 if (!FileExists("/usr/bin/ionice"))
110 pid_t Process
= ExecFork();
114 snprintf(buf
, sizeof(buf
), "-p%d", PID
);
116 Args
[0] = "/usr/bin/ionice";
120 execv(Args
[0], (char **)Args
);
122 return ExecWait(Process
, "ionice");
125 // DPkgPM::pkgDPkgPM - Constructor /*{{{*/
126 // ---------------------------------------------------------------------
128 pkgDPkgPM::pkgDPkgPM(pkgDepCache
*Cache
)
129 : pkgPackageManager(Cache
), PackagesDone(0), PackagesTotal(0)
131 d
= new pkgDPkgPMPrivate();
134 // DPkgPM::pkgDPkgPM - Destructor /*{{{*/
135 // ---------------------------------------------------------------------
137 pkgDPkgPM::~pkgDPkgPM()
142 // DPkgPM::Install - Install a package /*{{{*/
143 // ---------------------------------------------------------------------
144 /* Add an install operation to the sequence list */
145 bool pkgDPkgPM::Install(PkgIterator Pkg
,string File
)
147 if (File
.empty() == true || Pkg
.end() == true)
148 return _error
->Error("Internal Error, No file name for %s",Pkg
.Name());
150 // If the filename string begins with DPkg::Chroot-Directory, return the
151 // substr that is within the chroot so dpkg can access it.
152 string
const chrootdir
= _config
->FindDir("DPkg::Chroot-Directory","/");
153 if (chrootdir
!= "/" && File
.find(chrootdir
) == 0)
155 size_t len
= chrootdir
.length();
156 if (chrootdir
.at(len
- 1) == '/')
158 List
.push_back(Item(Item::Install
,Pkg
,File
.substr(len
)));
161 List
.push_back(Item(Item::Install
,Pkg
,File
));
166 // DPkgPM::Configure - Configure a package /*{{{*/
167 // ---------------------------------------------------------------------
168 /* Add a configure operation to the sequence list */
169 bool pkgDPkgPM::Configure(PkgIterator Pkg
)
171 if (Pkg
.end() == true)
174 List
.push_back(Item(Item::Configure
, Pkg
));
176 // Use triggers for config calls if we configure "smart"
177 // as otherwise Pre-Depends will not be satisfied, see #526774
178 if (_config
->FindB("DPkg::TriggersPending", false) == true)
179 List
.push_back(Item(Item::TriggersPending
, PkgIterator()));
184 // DPkgPM::Remove - Remove a package /*{{{*/
185 // ---------------------------------------------------------------------
186 /* Add a remove operation to the sequence list */
187 bool pkgDPkgPM::Remove(PkgIterator Pkg
,bool Purge
)
189 if (Pkg
.end() == true)
193 List
.push_back(Item(Item::Purge
,Pkg
));
195 List
.push_back(Item(Item::Remove
,Pkg
));
199 // DPkgPM::SendV2Pkgs - Send version 2 package info /*{{{*/
200 // ---------------------------------------------------------------------
201 /* This is part of the helper script communication interface, it sends
202 very complete information down to the other end of the pipe.*/
203 bool pkgDPkgPM::SendV2Pkgs(FILE *F
)
205 fprintf(F
,"VERSION 2\n");
207 /* Write out all of the configuration directives by walking the
208 configuration tree */
209 const Configuration::Item
*Top
= _config
->Tree(0);
212 if (Top
->Value
.empty() == false)
215 QuoteString(Top
->FullTag(),"=\"\n").c_str(),
216 QuoteString(Top
->Value
,"\n").c_str());
225 while (Top
!= 0 && Top
->Next
== 0)
232 // Write out the package actions in order.
233 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
235 if(I
->Pkg
.end() == true)
238 pkgDepCache::StateCache
&S
= Cache
[I
->Pkg
];
240 fprintf(F
,"%s ",I
->Pkg
.Name());
242 if (I
->Pkg
->CurrentVer
== 0)
245 fprintf(F
,"%s ",I
->Pkg
.CurrentVer().VerStr());
247 // Show the compare operator
249 if (S
.InstallVer
!= 0)
252 if (I
->Pkg
->CurrentVer
!= 0)
253 Comp
= S
.InstVerIter(Cache
).CompareVer(I
->Pkg
.CurrentVer());
260 fprintf(F
,"%s ",S
.InstVerIter(Cache
).VerStr());
265 // Show the filename/operation
266 if (I
->Op
== Item::Install
)
269 if (I
->File
[0] != '/')
270 fprintf(F
,"**ERROR**\n");
272 fprintf(F
,"%s\n",I
->File
.c_str());
274 if (I
->Op
== Item::Configure
)
275 fprintf(F
,"**CONFIGURE**\n");
276 if (I
->Op
== Item::Remove
||
277 I
->Op
== Item::Purge
)
278 fprintf(F
,"**REMOVE**\n");
286 // DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
287 // ---------------------------------------------------------------------
288 /* This looks for a list of scripts to run from the configuration file
289 each one is run and is fed on standard input a list of all .deb files
290 that are due to be installed. */
291 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf
)
293 Configuration::Item
const *Opts
= _config
->Tree(Cnf
);
294 if (Opts
== 0 || Opts
->Child
== 0)
298 unsigned int Count
= 1;
299 for (; Opts
!= 0; Opts
= Opts
->Next
, Count
++)
301 if (Opts
->Value
.empty() == true)
304 // Determine the protocol version
305 string OptSec
= Opts
->Value
;
306 string::size_type Pos
;
307 if ((Pos
= OptSec
.find(' ')) == string::npos
|| Pos
== 0)
308 Pos
= OptSec
.length();
309 OptSec
= "DPkg::Tools::Options::" + string(Opts
->Value
.c_str(),Pos
);
311 unsigned int Version
= _config
->FindI(OptSec
+"::Version",1);
315 if (pipe(Pipes
) != 0)
316 return _error
->Errno("pipe","Failed to create IPC pipe to subprocess");
317 SetCloseExec(Pipes
[0],true);
318 SetCloseExec(Pipes
[1],true);
320 // Purified Fork for running the script
321 pid_t Process
= ExecFork();
325 dup2(Pipes
[0],STDIN_FILENO
);
326 SetCloseExec(STDOUT_FILENO
,false);
327 SetCloseExec(STDIN_FILENO
,false);
328 SetCloseExec(STDERR_FILENO
,false);
330 if (_config
->FindDir("DPkg::Chroot-Directory","/") != "/")
332 std::cerr
<< "Chrooting into "
333 << _config
->FindDir("DPkg::Chroot-Directory")
335 if (chroot(_config
->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0)
342 Args
[2] = Opts
->Value
.c_str();
344 execv(Args
[0],(char **)Args
);
348 FILE *F
= fdopen(Pipes
[1],"w");
350 return _error
->Errno("fdopen","Faild to open new FD");
352 // Feed it the filenames.
355 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
357 // Only deal with packages to be installed from .deb
358 if (I
->Op
!= Item::Install
)
362 if (I
->File
[0] != '/')
365 /* Feed the filename of each package that is pending install
367 fprintf(F
,"%s\n",I
->File
.c_str());
377 // Clean up the sub process
378 if (ExecWait(Process
,Opts
->Value
.c_str()) == false)
379 return _error
->Error("Failure running script %s",Opts
->Value
.c_str());
385 // DPkgPM::DoStdin - Read stdin and pass to slave pty /*{{{*/
386 // ---------------------------------------------------------------------
389 void pkgDPkgPM::DoStdin(int master
)
391 unsigned char input_buf
[256] = {0,};
392 ssize_t len
= read(0, input_buf
, sizeof(input_buf
));
394 write(master
, input_buf
, len
);
396 d
->stdin_is_dev_null
= true;
399 // DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/
400 // ---------------------------------------------------------------------
402 * read the terminal pty and write log
404 void pkgDPkgPM::DoTerminalPty(int master
)
406 unsigned char term_buf
[1024] = {0,0, };
408 ssize_t len
=read(master
, term_buf
, sizeof(term_buf
));
409 if(len
== -1 && errno
== EIO
)
411 // this happens when the child is about to exit, we
412 // give it time to actually exit, otherwise we run
413 // into a race so we sleep for half a second.
414 struct timespec sleepfor
= { 0, 500000000 };
415 nanosleep(&sleepfor
, NULL
);
420 write(1, term_buf
, len
);
422 fwrite(term_buf
, len
, sizeof(char), d
->term_out
);
425 // DPkgPM::ProcessDpkgStatusBuf /*{{{*/
426 // ---------------------------------------------------------------------
429 void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd
, char *line
)
431 bool const Debug
= _config
->FindB("Debug::pkgDPkgProgressReporting",false);
432 // the status we output
433 ostringstream status
;
436 std::clog
<< "got from dpkg '" << line
<< "'" << std::endl
;
439 /* dpkg sends strings like this:
440 'status: <pkg>: <pkg qstate>'
441 errors look like this:
442 '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
443 and conffile-prompt like this
444 'status: conffile-prompt: conffile : 'current-conffile' 'new-conffile' useredited distedited
446 Newer versions of dpkg sent also:
447 'processing: install: pkg'
448 'processing: configure: pkg'
449 'processing: remove: pkg'
450 'processing: purge: pkg'
451 'processing: disappear: pkg'
452 'processing: trigproc: trigger'
456 // dpkg sends multiline error messages sometimes (see
457 // #374195 for a example. we should support this by
458 // either patching dpkg to not send multiline over the
459 // statusfd or by rewriting the code here to deal with
460 // it. for now we just ignore it and not crash
461 TokSplitString(':', line
, list
, sizeof(list
)/sizeof(list
[0]));
462 if( list
[0] == NULL
|| list
[1] == NULL
|| list
[2] == NULL
)
465 std::clog
<< "ignoring line: not enough ':'" << std::endl
;
468 const char* const pkg
= list
[1];
469 const char* action
= _strstrip(list
[2]);
471 // 'processing' from dpkg looks like
472 // 'processing: action: pkg'
473 if(strncmp(list
[0], "processing", strlen("processing")) == 0)
476 const char* const pkg_or_trigger
= _strstrip(list
[2]);
477 action
= _strstrip( list
[1]);
478 const std::pair
<const char *, const char *> * const iter
=
479 std::find_if(PackageProcessingOpsBegin
,
480 PackageProcessingOpsEnd
,
481 MatchProcessingOp(action
));
482 if(iter
== PackageProcessingOpsEnd
)
485 std::clog
<< "ignoring unknown action: " << action
<< std::endl
;
488 snprintf(s
, sizeof(s
), _(iter
->second
), pkg_or_trigger
);
490 status
<< "pmstatus:" << pkg_or_trigger
491 << ":" << (PackagesDone
/float(PackagesTotal
)*100.0)
495 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
497 std::clog
<< "send: '" << status
.str() << "'" << endl
;
499 if (strncmp(action
, "disappear", strlen("disappear")) == 0)
500 handleDisappearAction(pkg_or_trigger
);
504 if(strncmp(action
,"error",strlen("error")) == 0)
506 // urgs, sometime has ":" in its error string so that we
507 // end up with the error message split between list[3]
508 // and list[4], e.g. the message:
509 // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..."
511 if( list
[4] != NULL
)
512 list
[3][strlen(list
[3])] = ':';
514 status
<< "pmerror:" << list
[1]
515 << ":" << (PackagesDone
/float(PackagesTotal
)*100.0)
519 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
521 std::clog
<< "send: '" << status
.str() << "'" << endl
;
523 WriteApportReport(list
[1], list
[3]);
526 else if(strncmp(action
,"conffile",strlen("conffile")) == 0)
528 status
<< "pmconffile:" << list
[1]
529 << ":" << (PackagesDone
/float(PackagesTotal
)*100.0)
533 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
535 std::clog
<< "send: '" << status
.str() << "'" << endl
;
539 vector
<struct DpkgState
> const &states
= PackageOps
[pkg
];
540 const char *next_action
= NULL
;
541 if(PackageOpsDone
[pkg
] < states
.size())
542 next_action
= states
[PackageOpsDone
[pkg
]].state
;
543 // check if the package moved to the next dpkg state
544 if(next_action
&& (strcmp(action
, next_action
) == 0))
546 // only read the translation if there is actually a next
548 const char *translation
= _(states
[PackageOpsDone
[pkg
]].str
);
550 snprintf(s
, sizeof(s
), translation
, pkg
);
552 // we moved from one dpkg state to a new one, report that
553 PackageOpsDone
[pkg
]++;
555 // build the status str
556 status
<< "pmstatus:" << pkg
557 << ":" << (PackagesDone
/float(PackagesTotal
)*100.0)
561 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
563 std::clog
<< "send: '" << status
.str() << "'" << endl
;
566 std::clog
<< "(parsed from dpkg) pkg: " << pkg
567 << " action: " << action
<< endl
;
570 // DPkgPM::handleDisappearAction /*{{{*/
571 void pkgDPkgPM::handleDisappearAction(string
const &pkgname
)
573 // record the package name for display and stuff later
574 disappearedPkgs
.insert(pkgname
);
576 pkgCache::PkgIterator Pkg
= Cache
.FindPkg(pkgname
);
577 if (unlikely(Pkg
.end() == true))
579 // the disappeared package was auto-installed - nothing to do
580 if ((Cache
[Pkg
].Flags
& pkgCache::Flag::Auto
) == pkgCache::Flag::Auto
)
582 pkgCache::VerIterator PkgVer
= Cache
[Pkg
].InstVerIter(Cache
);
583 if (unlikely(PkgVer
.end() == true))
585 /* search in the list of dependencies for (Pre)Depends,
586 check if this dependency has a Replaces on our package
587 and if so transfer the manual installed flag to it */
588 for (pkgCache::DepIterator Dep
= PkgVer
.DependsList(); Dep
.end() != true; ++Dep
)
590 if (Dep
->Type
!= pkgCache::Dep::Depends
&&
591 Dep
->Type
!= pkgCache::Dep::PreDepends
)
593 pkgCache::PkgIterator Tar
= Dep
.TargetPkg();
594 if (unlikely(Tar
.end() == true))
596 // the package is already marked as manual
597 if ((Cache
[Tar
].Flags
& pkgCache::Flag::Auto
) != pkgCache::Flag::Auto
)
599 pkgCache::VerIterator TarVer
= Cache
[Tar
].InstVerIter(Cache
);
600 if (TarVer
.end() == true)
602 for (pkgCache::DepIterator Rep
= TarVer
.DependsList(); Rep
.end() != true; ++Rep
)
604 if (Rep
->Type
!= pkgCache::Dep::Replaces
)
606 if (Pkg
!= Rep
.TargetPkg())
608 // okay, they are strongly connected - transfer manual-bit
610 std::clog
<< "transfer manual-bit from disappeared »" << pkgname
<< "« to »" << Tar
.FullName() << "«" << std::endl
;
611 Cache
[Tar
].Flags
&= ~Flag::Auto
;
617 // DPkgPM::DoDpkgStatusFd /*{{{*/
618 // ---------------------------------------------------------------------
621 void pkgDPkgPM::DoDpkgStatusFd(int statusfd
, int OutStatusFd
)
626 len
=read(statusfd
, &d
->dpkgbuf
[d
->dpkgbuf_pos
], sizeof(d
->dpkgbuf
)-d
->dpkgbuf_pos
);
627 d
->dpkgbuf_pos
+= len
;
631 // process line by line if we have a buffer
633 while((q
=(char*)memchr(p
, '\n', d
->dpkgbuf
+d
->dpkgbuf_pos
-p
)) != NULL
)
636 ProcessDpkgStatusLine(OutStatusFd
, p
);
637 p
=q
+1; // continue with next line
640 // now move the unprocessed bits (after the final \n that is now a 0x0)
641 // to the start and update d->dpkgbuf_pos
642 p
= (char*)memrchr(d
->dpkgbuf
, 0, d
->dpkgbuf_pos
);
646 // we are interessted in the first char *after* 0x0
649 // move the unprocessed tail to the start and update pos
650 memmove(d
->dpkgbuf
, p
, p
-d
->dpkgbuf
);
651 d
->dpkgbuf_pos
= d
->dpkgbuf
+d
->dpkgbuf_pos
-p
;
654 // DPkgPM::WriteHistoryTag /*{{{*/
655 void pkgDPkgPM::WriteHistoryTag(string
const &tag
, string value
)
657 size_t const length
= value
.length();
660 // poor mans rstrip(", ")
661 if (value
[length
-2] == ',' && value
[length
-1] == ' ')
662 value
.erase(length
- 2, 2);
663 fprintf(d
->history_out
, "%s: %s\n", tag
.c_str(), value
.c_str());
665 // DPkgPM::OpenLog /*{{{*/
666 bool pkgDPkgPM::OpenLog()
668 string
const logdir
= _config
->FindDir("Dir::Log");
669 if(CreateAPTDirectoryIfNeeded(logdir
, logdir
) == false)
670 // FIXME: use a better string after freeze
671 return _error
->Error(_("Directory '%s' missing"), logdir
.c_str());
675 time_t const t
= time(NULL
);
676 struct tm
const * const tmp
= localtime(&t
);
677 strftime(timestr
, sizeof(timestr
), "%F %T", tmp
);
680 string
const logfile_name
= flCombine(logdir
,
681 _config
->Find("Dir::Log::Terminal"));
682 if (!logfile_name
.empty())
684 d
->term_out
= fopen(logfile_name
.c_str(),"a");
685 if (d
->term_out
== NULL
)
686 return _error
->WarningE("OpenLog", _("Could not open file '%s'"), logfile_name
.c_str());
687 setvbuf(d
->term_out
, NULL
, _IONBF
, 0);
688 SetCloseExec(fileno(d
->term_out
), true);
691 pw
= getpwnam("root");
692 gr
= getgrnam("adm");
693 if (pw
!= NULL
&& gr
!= NULL
)
694 chown(logfile_name
.c_str(), pw
->pw_uid
, gr
->gr_gid
);
695 chmod(logfile_name
.c_str(), 0644);
696 fprintf(d
->term_out
, "\nLog started: %s\n", timestr
);
699 // write your history
700 string
const history_name
= flCombine(logdir
,
701 _config
->Find("Dir::Log::History"));
702 if (!history_name
.empty())
704 d
->history_out
= fopen(history_name
.c_str(),"a");
705 if (d
->history_out
== NULL
)
706 return _error
->WarningE("OpenLog", _("Could not open file '%s'"), history_name
.c_str());
707 chmod(history_name
.c_str(), 0644);
708 fprintf(d
->history_out
, "\nStart-Date: %s\n", timestr
);
709 string remove
, purge
, install
, reinstall
, upgrade
, downgrade
;
710 for (pkgCache::PkgIterator I
= Cache
.PkgBegin(); I
.end() == false; ++I
)
712 enum { CANDIDATE
, CANDIDATE_AUTO
, CURRENT_CANDIDATE
, CURRENT
} infostring
;
714 #define HISTORYINFO(X, Y) { line = &X; infostring = Y; }
715 if (Cache
[I
].NewInstall() == true)
716 HISTORYINFO(install
, CANDIDATE_AUTO
)
717 else if (Cache
[I
].ReInstall() == true)
718 HISTORYINFO(reinstall
, CANDIDATE
)
719 else if (Cache
[I
].Upgrade() == true)
720 HISTORYINFO(upgrade
, CURRENT_CANDIDATE
)
721 else if (Cache
[I
].Downgrade() == true)
722 HISTORYINFO(downgrade
, CURRENT_CANDIDATE
)
723 else if (Cache
[I
].Delete() == true)
724 HISTORYINFO((Cache
[I
].Purge() ? purge
: remove
), CURRENT
)
728 line
->append(I
.FullName(false)).append(" (");
729 switch (infostring
) {
730 case CANDIDATE
: line
->append(Cache
[I
].CandVersion
); break;
732 line
->append(Cache
[I
].CandVersion
);
733 if ((Cache
[I
].Flags
& pkgCache::Flag::Auto
) == pkgCache::Flag::Auto
)
734 line
->append(", automatic");
736 case CURRENT_CANDIDATE
: line
->append(Cache
[I
].CurVersion
).append(", ").append(Cache
[I
].CandVersion
); break;
737 case CURRENT
: line
->append(Cache
[I
].CurVersion
); break;
741 if (_config
->Exists("Commandline::AsString") == true)
742 WriteHistoryTag("Commandline", _config
->Find("Commandline::AsString"));
743 WriteHistoryTag("Install", install
);
744 WriteHistoryTag("Reinstall", reinstall
);
745 WriteHistoryTag("Upgrade", upgrade
);
746 WriteHistoryTag("Downgrade",downgrade
);
747 WriteHistoryTag("Remove",remove
);
748 WriteHistoryTag("Purge",purge
);
749 fflush(d
->history_out
);
755 // DPkg::CloseLog /*{{{*/
756 bool pkgDPkgPM::CloseLog()
759 time_t t
= time(NULL
);
760 struct tm
*tmp
= localtime(&t
);
761 strftime(timestr
, sizeof(timestr
), "%F %T", tmp
);
765 fprintf(d
->term_out
, "Log ended: ");
766 fprintf(d
->term_out
, "%s", timestr
);
767 fprintf(d
->term_out
, "\n");
774 if (disappearedPkgs
.empty() == false)
777 for (std::set
<std::string
>::const_iterator d
= disappearedPkgs
.begin();
778 d
!= disappearedPkgs
.end(); ++d
)
780 pkgCache::PkgIterator P
= Cache
.FindPkg(*d
);
781 disappear
.append(*d
);
783 disappear
.append(", ");
785 disappear
.append(" (").append(Cache
[P
].CurVersion
).append("), ");
787 WriteHistoryTag("Disappeared", disappear
);
789 if (d
->dpkg_error
.empty() == false)
790 fprintf(d
->history_out
, "Error: %s\n", d
->dpkg_error
.c_str());
791 fprintf(d
->history_out
, "End-Date: %s\n", timestr
);
792 fclose(d
->history_out
);
794 d
->history_out
= NULL
;
800 // This implements a racy version of pselect for those architectures
801 // that don't have a working implementation.
802 // FIXME: Probably can be removed on Lenny+1
803 static int racy_pselect(int nfds
, fd_set
*readfds
, fd_set
*writefds
,
804 fd_set
*exceptfds
, const struct timespec
*timeout
,
805 const sigset_t
*sigmask
)
811 tv
.tv_sec
= timeout
->tv_sec
;
812 tv
.tv_usec
= timeout
->tv_nsec
/1000;
814 sigprocmask(SIG_SETMASK
, sigmask
, &origmask
);
815 retval
= select(nfds
, readfds
, writefds
, exceptfds
, &tv
);
816 sigprocmask(SIG_SETMASK
, &origmask
, 0);
820 // DPkgPM::Go - Run the sequence /*{{{*/
821 // ---------------------------------------------------------------------
822 /* This globs the operations and calls dpkg
824 * If it is called with "OutStatusFd" set to a valid file descriptor
825 * apt will report the install progress over this fd. It maps the
826 * dpkg states a package goes through to human readable (and i10n-able)
827 * names and calculates a percentage for each step.
829 bool pkgDPkgPM::Go(int OutStatusFd
)
834 sigset_t original_sigmask
;
836 unsigned int const MaxArgs
= _config
->FindI("Dpkg::MaxArgs",8*1024);
837 unsigned int const MaxArgBytes
= _config
->FindI("Dpkg::MaxArgBytes",32*1024);
838 bool const NoTriggers
= _config
->FindB("DPkg::NoTriggers", false);
840 if (RunScripts("DPkg::Pre-Invoke") == false)
843 if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
846 // support subpressing of triggers processing for special
847 // cases like d-i that runs the triggers handling manually
848 bool const SmartConf
= (_config
->Find("PackageManager::Configure", "all") != "all");
849 bool const TriggersPending
= _config
->FindB("DPkg::TriggersPending", false);
850 if (_config
->FindB("DPkg::ConfigurePending", SmartConf
) == true)
851 List
.push_back(Item(Item::ConfigurePending
, PkgIterator()));
853 // map the dpkg states to the operations that are performed
854 // (this is sorted in the same way as Item::Ops)
855 static const struct DpkgState DpkgStatesOpMap
[][7] = {
858 {"half-installed", N_("Preparing %s")},
859 {"unpacked", N_("Unpacking %s") },
862 // Configure operation
864 {"unpacked",N_("Preparing to configure %s") },
865 {"half-configured", N_("Configuring %s") },
866 { "installed", N_("Installed %s")},
871 {"half-configured", N_("Preparing for removal of %s")},
872 {"half-installed", N_("Removing %s")},
873 {"config-files", N_("Removed %s")},
878 {"config-files", N_("Preparing to completely remove %s")},
879 {"not-installed", N_("Completely removed %s")},
884 // init the PackageOps map, go over the list of packages that
885 // that will be [installed|configured|removed|purged] and add
886 // them to the PackageOps map (the dpkg states it goes through)
887 // and the PackageOpsTranslations (human readable strings)
888 for (vector
<Item
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
890 if((*I
).Pkg
.end() == true)
893 string
const name
= (*I
).Pkg
.Name();
894 PackageOpsDone
[name
] = 0;
895 for(int i
=0; (DpkgStatesOpMap
[(*I
).Op
][i
]).state
!= NULL
; ++i
)
897 PackageOps
[name
].push_back(DpkgStatesOpMap
[(*I
).Op
][i
]);
902 d
->stdin_is_dev_null
= false;
907 // Generate the base argument list for dpkg
908 std::vector
<const char *> Args
;
909 unsigned long StartSize
= 0;
910 string
const Tmp
= _config
->Find("Dir::Bin::dpkg","dpkg");
911 Args
.push_back(Tmp
.c_str());
912 StartSize
+= Tmp
.length();
914 // Stick in any custom dpkg options
915 Configuration::Item
const *Opts
= _config
->Tree("DPkg::Options");
919 for (; Opts
!= 0; Opts
= Opts
->Next
)
921 if (Opts
->Value
.empty() == true)
923 Args
.push_back(Opts
->Value
.c_str());
924 StartSize
+= Opts
->Value
.length();
927 size_t const BaseArgs
= Args
.size();
929 // this loop is runs once per operation
930 for (vector
<Item
>::const_iterator I
= List
.begin(); I
!= List
.end();)
932 // Do all actions with the same Op in one run
933 vector
<Item
>::const_iterator J
= I
;
934 if (TriggersPending
== true)
935 for (; J
!= List
.end(); ++J
)
939 if (J
->Op
!= Item::TriggersPending
)
941 vector
<Item
>::const_iterator T
= J
+ 1;
942 if (T
!= List
.end() && T
->Op
== I
->Op
)
947 for (; J
!= List
.end() && J
->Op
== I
->Op
; ++J
)
950 // keep track of allocated strings for multiarch package names
951 std::vector
<char *> Packages
;
953 // start with the baseset of arguments
954 unsigned long Size
= StartSize
;
955 Args
.erase(Args
.begin() + BaseArgs
, Args
.end());
957 // Now check if we are within the MaxArgs limit
959 // this code below is problematic, because it may happen that
960 // the argument list is split in a way that A depends on B
961 // and they are in the same "--configure A B" run
962 // - with the split they may now be configured in different
963 // runs, using Immediate-Configure-All can help prevent this.
964 if (J
- I
> (signed)MaxArgs
)
967 Args
.reserve(MaxArgs
+ 10);
971 Args
.reserve((J
- I
) + 10);
978 #define ADDARG(X) Args.push_back(X); Size += strlen(X)
979 #define ADDARGC(X) Args.push_back(X); Size += sizeof(X) - 1
981 ADDARGC("--status-fd");
982 char status_fd_buf
[20];
983 snprintf(status_fd_buf
,sizeof(status_fd_buf
),"%i", fd
[1]);
984 ADDARG(status_fd_buf
);
985 unsigned long const Op
= I
->Op
;
990 ADDARGC("--force-depends");
991 ADDARGC("--force-remove-essential");
996 ADDARGC("--force-depends");
997 ADDARGC("--force-remove-essential");
1001 case Item::Configure
:
1002 ADDARGC("--configure");
1005 case Item::ConfigurePending
:
1006 ADDARGC("--configure");
1007 ADDARGC("--pending");
1010 case Item::TriggersPending
:
1011 ADDARGC("--triggers-only");
1012 ADDARGC("--pending");
1016 ADDARGC("--unpack");
1017 ADDARGC("--auto-deconfigure");
1021 if (NoTriggers
== true && I
->Op
!= Item::TriggersPending
&&
1022 I
->Op
!= Item::ConfigurePending
)
1024 ADDARGC("--no-triggers");
1028 // Write in the file or package names
1029 if (I
->Op
== Item::Install
)
1031 for (;I
!= J
&& Size
< MaxArgBytes
; ++I
)
1033 if (I
->File
[0] != '/')
1034 return _error
->Error("Internal Error, Pathname to install is not absolute '%s'",I
->File
.c_str());
1035 Args
.push_back(I
->File
.c_str());
1036 Size
+= I
->File
.length();
1041 string
const nativeArch
= _config
->Find("APT::Architecture");
1042 unsigned long const oldSize
= I
->Op
== Item::Configure
? Size
: 0;
1043 for (;I
!= J
&& Size
< MaxArgBytes
; ++I
)
1045 if((*I
).Pkg
.end() == true)
1047 if (I
->Op
== Item::Configure
&& disappearedPkgs
.find(I
->Pkg
.Name()) != disappearedPkgs
.end())
1049 if (I
->Pkg
.Arch() == nativeArch
|| !strcmp(I
->Pkg
.Arch(), "all"))
1051 char const * const name
= I
->Pkg
.Name();
1056 char * const fullname
= strdup(I
->Pkg
.FullName(false).c_str());
1057 Packages
.push_back(fullname
);
1061 // skip configure action if all sheduled packages disappeared
1062 if (oldSize
== Size
)
1069 if (_config
->FindB("Debug::pkgDPkgPM",false) == true)
1071 for (std::vector
<const char *>::const_iterator a
= Args
.begin();
1072 a
!= Args
.end(); ++a
)
1077 Args
.push_back(NULL
);
1083 /* Mask off sig int/quit. We do this because dpkg also does when
1084 it forks scripts. What happens is that when you hit ctrl-c it sends
1085 it to all processes in the group. Since dpkg ignores the signal
1086 it doesn't die but we do! So we must also ignore it */
1087 sighandler_t old_SIGQUIT
= signal(SIGQUIT
,SIG_IGN
);
1088 sighandler_t old_SIGINT
= signal(SIGINT
,SigINT
);
1090 // Check here for any SIGINT
1091 if (pkgPackageManager::SigINTStop
&& (Op
== Item::Remove
|| Op
== Item::Purge
|| Op
== Item::Install
))
1095 // ignore SIGHUP as well (debian #463030)
1096 sighandler_t old_SIGHUP
= signal(SIGHUP
,SIG_IGN
);
1103 // if tcgetattr does not return zero there was a error
1104 // and we do not do any pty magic
1105 if (tcgetattr(0, &tt
) == 0)
1107 ioctl(0, TIOCGWINSZ
, (char *)&win
);
1108 if (openpty(&master
, &slave
, NULL
, &tt
, &win
) < 0)
1110 const char *s
= _("Can not write log, openpty() "
1111 "failed (/dev/pts not mounted?)\n");
1112 fprintf(stderr
, "%s",s
);
1114 fprintf(d
->term_out
, "%s",s
);
1115 master
= slave
= -1;
1120 rtt
.c_lflag
&= ~ECHO
;
1121 rtt
.c_lflag
|= ISIG
;
1122 // block SIGTTOU during tcsetattr to prevent a hang if
1123 // the process is a member of the background process group
1124 // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html
1125 sigemptyset(&sigmask
);
1126 sigaddset(&sigmask
, SIGTTOU
);
1127 sigprocmask(SIG_BLOCK
,&sigmask
, &original_sigmask
);
1128 tcsetattr(0, TCSAFLUSH
, &rtt
);
1129 sigprocmask(SIG_SETMASK
, &original_sigmask
, 0);
1134 _config
->Set("APT::Keep-Fds::",fd
[1]);
1135 // send status information that we are about to fork dpkg
1136 if(OutStatusFd
> 0) {
1137 ostringstream status
;
1138 status
<< "pmstatus:dpkg-exec:"
1139 << (PackagesDone
/float(PackagesTotal
)*100.0)
1140 << ":" << _("Running dpkg")
1142 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
1146 // This is the child
1149 if(slave
>= 0 && master
>= 0)
1152 ioctl(slave
, TIOCSCTTY
, 0);
1159 close(fd
[0]); // close the read end of the pipe
1161 if (_config
->FindDir("DPkg::Chroot-Directory","/") != "/")
1163 std::cerr
<< "Chrooting into "
1164 << _config
->FindDir("DPkg::Chroot-Directory")
1166 if (chroot(_config
->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0)
1170 if (chdir(_config
->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
1173 if (_config
->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO
))
1176 if ((Flags
= fcntl(STDIN_FILENO
,F_GETFL
,dummy
)) < 0)
1179 // Discard everything in stdin before forking dpkg
1180 if (fcntl(STDIN_FILENO
,F_SETFL
,Flags
| O_NONBLOCK
) < 0)
1183 while (read(STDIN_FILENO
,&dummy
,1) == 1);
1185 if (fcntl(STDIN_FILENO
,F_SETFL
,Flags
& (~(long)O_NONBLOCK
)) < 0)
1189 /* No Job Control Stop Env is a magic dpkg var that prevents it
1190 from using sigstop */
1191 putenv((char *)"DPKG_NO_TSTP=yes");
1192 execvp(Args
[0], (char**) &Args
[0]);
1193 cerr
<< "Could not exec dpkg!" << endl
;
1198 if (_config
->FindB("DPkg::UseIoNice", false) == true)
1201 // clear the Keep-Fd again
1202 _config
->Clear("APT::Keep-Fds",fd
[1]);
1207 // we read from dpkg here
1208 int const _dpkgin
= fd
[0];
1209 close(fd
[1]); // close the write end of the pipe
1215 sigemptyset(&sigmask
);
1216 sigprocmask(SIG_BLOCK
,&sigmask
,&original_sigmask
);
1218 /* free vectors (and therefore memory) as we don't need the included data anymore */
1219 for (std::vector
<char *>::const_iterator p
= Packages
.begin();
1220 p
!= Packages
.end(); ++p
)
1224 // the result of the waitpid call
1227 while ((res
=waitpid(Child
,&Status
, WNOHANG
)) != Child
) {
1229 // FIXME: move this to a function or something, looks ugly here
1230 // error handling, waitpid returned -1
1233 RunScripts("DPkg::Post-Invoke");
1235 // Restore sig int/quit
1236 signal(SIGQUIT
,old_SIGQUIT
);
1237 signal(SIGINT
,old_SIGINT
);
1239 signal(SIGHUP
,old_SIGHUP
);
1240 return _error
->Errno("waitpid","Couldn't wait for subprocess");
1243 // wait for input or output here
1245 if (master
>= 0 && !d
->stdin_is_dev_null
)
1247 FD_SET(_dpkgin
, &rfds
);
1249 FD_SET(master
, &rfds
);
1252 select_ret
= pselect(max(master
, _dpkgin
)+1, &rfds
, NULL
, NULL
,
1253 &tv
, &original_sigmask
);
1254 if (select_ret
< 0 && (errno
== EINVAL
|| errno
== ENOSYS
))
1255 select_ret
= racy_pselect(max(master
, _dpkgin
)+1, &rfds
, NULL
,
1256 NULL
, &tv
, &original_sigmask
);
1257 if (select_ret
== 0)
1259 else if (select_ret
< 0 && errno
== EINTR
)
1261 else if (select_ret
< 0)
1263 perror("select() returned error");
1267 if(master
>= 0 && FD_ISSET(master
, &rfds
))
1268 DoTerminalPty(master
);
1269 if(master
>= 0 && FD_ISSET(0, &rfds
))
1271 if(FD_ISSET(_dpkgin
, &rfds
))
1272 DoDpkgStatusFd(_dpkgin
, OutStatusFd
);
1276 // Restore sig int/quit
1277 signal(SIGQUIT
,old_SIGQUIT
);
1278 signal(SIGINT
,old_SIGINT
);
1280 signal(SIGHUP
,old_SIGHUP
);
1284 tcsetattr(0, TCSAFLUSH
, &tt
);
1288 // Check for an error code.
1289 if (WIFEXITED(Status
) == 0 || WEXITSTATUS(Status
) != 0)
1291 // if it was set to "keep-dpkg-runing" then we won't return
1292 // here but keep the loop going and just report it as a error
1294 bool const stopOnError
= _config
->FindB("Dpkg::StopOnError",true);
1297 RunScripts("DPkg::Post-Invoke");
1299 if (WIFSIGNALED(Status
) != 0 && WTERMSIG(Status
) == SIGSEGV
)
1300 strprintf(d
->dpkg_error
, "Sub-process %s received a segmentation fault.",Args
[0]);
1301 else if (WIFEXITED(Status
) != 0)
1302 strprintf(d
->dpkg_error
, "Sub-process %s returned an error code (%u)",Args
[0],WEXITSTATUS(Status
));
1304 strprintf(d
->dpkg_error
, "Sub-process %s exited unexpectedly",Args
[0]);
1306 if(d
->dpkg_error
.size() > 0)
1307 _error
->Error("%s", d
->dpkg_error
.c_str());
1318 if (pkgPackageManager::SigINTStop
)
1319 _error
->Warning(_("Operation was interrupted before it could finish"));
1321 if (RunScripts("DPkg::Post-Invoke") == false)
1324 if (_config
->FindB("Debug::pkgDPkgPM",false) == false)
1326 std::string
const oldpkgcache
= _config
->FindFile("Dir::cache::pkgcache");
1327 if (oldpkgcache
.empty() == false && RealFileExists(oldpkgcache
) == true &&
1328 unlink(oldpkgcache
.c_str()) == 0)
1330 std::string
const srcpkgcache
= _config
->FindFile("Dir::cache::srcpkgcache");
1331 if (srcpkgcache
.empty() == false && RealFileExists(srcpkgcache
) == true)
1333 _error
->PushToStack();
1334 pkgCacheFile CacheFile
;
1335 CacheFile
.BuildCaches(NULL
, true);
1336 _error
->RevertToStack();
1341 Cache
.writeStateFile(NULL
);
1345 void SigINT(int sig
) {
1346 if (_config
->FindB("APT::Immediate-Configure-All",false))
1347 pkgPackageManager::SigINTStop
= true;
1350 // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/
1351 // ---------------------------------------------------------------------
1353 void pkgDPkgPM::Reset()
1355 List
.erase(List
.begin(),List
.end());
1358 // pkgDpkgPM::WriteApportReport - write out error report pkg failure /*{{{*/
1359 // ---------------------------------------------------------------------
1361 void pkgDPkgPM::WriteApportReport(const char *pkgpath
, const char *errormsg
)
1363 string pkgname
, reportfile
, srcpkgname
, pkgver
, arch
;
1364 string::size_type pos
;
1367 if (_config
->FindB("Dpkg::ApportFailureReport", true) == false)
1369 std::clog
<< "configured to not write apport reports" << std::endl
;
1373 // only report the first errors
1374 if(pkgFailures
> _config
->FindI("APT::Apport::MaxReports", 3))
1376 std::clog
<< _("No apport report written because MaxReports is reached already") << std::endl
;
1380 // check if its not a follow up error
1381 const char *needle
= dgettext("dpkg", "dependency problems - leaving unconfigured");
1382 if(strstr(errormsg
, needle
) != NULL
) {
1383 std::clog
<< _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl
;
1387 // do not report disk-full failures
1388 if(strstr(errormsg
, strerror(ENOSPC
)) != NULL
) {
1389 std::clog
<< _("No apport report written because the error message indicates a disk full error") << std::endl
;
1393 // do not report out-of-memory failures
1394 if(strstr(errormsg
, strerror(ENOMEM
)) != NULL
||
1395 strstr(errormsg
, "failed to allocate memory") != NULL
) {
1396 std::clog
<< _("No apport report written because the error message indicates a out of memory error") << std::endl
;
1400 // do not report bugs regarding inaccessible local files
1401 if(strstr(errormsg
, strerror(ENOENT
)) != NULL
||
1402 strstr(errormsg
, "cannot access archive") != NULL
) {
1403 std::clog
<< _("No apport report written because the error message indicates an issue on the local system") << std::endl
;
1407 // do not report errors encountered when decompressing packages
1408 if(strstr(errormsg
, "--fsys-tarfile returned error exit status 2") != NULL
) {
1409 std::clog
<< _("No apport report written because the error message indicates an issue on the local system") << std::endl
;
1413 // do not report dpkg I/O errors, this is a format string, so we compare
1414 // the prefix and the suffix of the error with the dpkg error message
1415 vector
<string
> io_errors
;
1416 io_errors
.push_back(string("failed to read on buffer copy for %s"));
1417 io_errors
.push_back(string("failed in write on buffer copy for %s"));
1418 io_errors
.push_back(string("short read on buffer copy for %s"));
1420 for (vector
<string
>::iterator I
= io_errors
.begin(); I
!= io_errors
.end(); I
++)
1422 vector
<string
> list
= VectorizeString(dgettext("dpkg", (*I
).c_str()), '%');
1423 if (list
.size() > 1) {
1424 // we need to split %s, VectorizeString only allows char so we need
1425 // to kill the "s" manually
1426 if (list
[1].size() > 1) {
1427 list
[1].erase(0, 1);
1428 if(strstr(errormsg
, list
[0].c_str()) &&
1429 strstr(errormsg
, list
[1].c_str())) {
1430 std::clog
<< _("No apport report written because the error message indicates a dpkg I/O error") << std::endl
;
1437 // get the pkgname and reportfile
1438 pkgname
= flNotDir(pkgpath
);
1439 pos
= pkgname
.find('_');
1440 if(pos
!= string::npos
)
1441 pkgname
= pkgname
.substr(0, pos
);
1443 // find the package versin and source package name
1444 pkgCache::PkgIterator Pkg
= Cache
.FindPkg(pkgname
);
1445 if (Pkg
.end() == true)
1447 pkgCache::VerIterator Ver
= Cache
.GetCandidateVer(Pkg
);
1448 if (Ver
.end() == true)
1450 pkgver
= Ver
.VerStr() == NULL
? "unknown" : Ver
.VerStr();
1451 pkgRecords
Recs(Cache
);
1452 pkgRecords::Parser
&Parse
= Recs
.Lookup(Ver
.FileList());
1453 srcpkgname
= Parse
.SourcePkg();
1454 if(srcpkgname
.empty())
1455 srcpkgname
= pkgname
;
1457 // if the file exists already, we check:
1458 // - if it was reported already (touched by apport).
1459 // If not, we do nothing, otherwise
1460 // we overwrite it. This is the same behaviour as apport
1461 // - if we have a report with the same pkgversion already
1463 reportfile
= flCombine("/var/crash",pkgname
+".0.crash");
1464 if(FileExists(reportfile
))
1469 // check atime/mtime
1470 stat(reportfile
.c_str(), &buf
);
1471 if(buf
.st_mtime
> buf
.st_atime
)
1474 // check if the existing report is the same version
1475 report
= fopen(reportfile
.c_str(),"r");
1476 while(fgets(strbuf
, sizeof(strbuf
), report
) != NULL
)
1478 if(strstr(strbuf
,"Package:") == strbuf
)
1480 char pkgname
[255], version
[255];
1481 if(sscanf(strbuf
, "Package: %s %s", pkgname
, version
) == 2)
1482 if(strcmp(pkgver
.c_str(), version
) == 0)
1492 // now write the report
1493 arch
= _config
->Find("APT::Architecture");
1494 report
= fopen(reportfile
.c_str(),"w");
1497 if(_config
->FindB("DPkgPM::InitialReportOnly",false) == true)
1498 chmod(reportfile
.c_str(), 0);
1500 chmod(reportfile
.c_str(), 0600);
1501 fprintf(report
, "ProblemType: Package\n");
1502 fprintf(report
, "Architecture: %s\n", arch
.c_str());
1503 time_t now
= time(NULL
);
1504 fprintf(report
, "Date: %s" , ctime(&now
));
1505 fprintf(report
, "Package: %s %s\n", pkgname
.c_str(), pkgver
.c_str());
1506 fprintf(report
, "SourcePackage: %s\n", srcpkgname
.c_str());
1507 fprintf(report
, "ErrorMessage:\n %s\n", errormsg
);
1509 // ensure that the log is flushed
1511 fflush(d
->term_out
);
1513 // attach terminal log it if we have it
1514 string logfile_name
= _config
->FindFile("Dir::Log::Terminal");
1515 if (!logfile_name
.empty())
1520 fprintf(report
, "DpkgTerminalLog:\n");
1521 log
= fopen(logfile_name
.c_str(),"r");
1524 while( fgets(buf
, sizeof(buf
), log
) != NULL
)
1525 fprintf(report
, " %s", buf
);
1526 fprintf(report
, " \n");
1531 // attach history log it if we have it
1532 string histfile_name
= _config
->FindFile("Dir::Log::History");
1533 if (!histfile_name
.empty())
1538 fprintf(report
, "DpkgHistoryLog:\n");
1539 log
= fopen(histfile_name
.c_str(),"r");
1542 while( fgets(buf
, sizeof(buf
), log
) != NULL
)
1543 fprintf(report
, " %s", buf
);
1549 const char *ops_str
[] = {"Install", "Configure","Remove","Purge"};
1550 fprintf(report
, "AptOrdering:\n");
1551 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
1552 fprintf(report
, " %s: %s\n", (*I
).Pkg
.Name(), ops_str
[(*I
).Op
]);
1554 // attach dmesg log (to learn about segfaults)
1555 if (FileExists("/bin/dmesg"))
1560 fprintf(report
, "Dmesg:\n");
1561 log
= popen("/bin/dmesg","r");
1564 while( fgets(buf
, sizeof(buf
), log
) != NULL
)
1565 fprintf(report
, " %s", buf
);
1570 // attach df -l log (to learn about filesystem status)
1571 if (FileExists("/bin/df"))
1576 fprintf(report
, "Df:\n");
1577 log
= popen("/bin/df -l","r");
1580 while( fgets(buf
, sizeof(buf
), log
) != NULL
)
1581 fprintf(report
, " %s", buf
);