X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/77fdc2a4de43144432ef9e74058634e94e45ef65..f01f5d911d6a9731893865bb1ec617c5038add3e:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index b4d812d26..7355af9d5 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -10,71 +10,97 @@ // Includes /*{{{*/ #include -#include -#include +#include #include #include -#include -#include +#include +#include +#include #include -#include +#include #include +#include +#include +#include +#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include /*}}}*/ using namespace std; +APT_PURE static unsigned int +EnvironmentSize() +{ + unsigned int size = 0; + char **envp = environ; + + while (*envp != NULL) + size += strlen (*envp++) + 1; + + return size; +} + class pkgDPkgPMPrivate { public: pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0), - term_out(NULL), history_out(NULL), - last_reported_progress(0.0), nr_terminal_rows(0), - fancy_progress_output(false) + term_out(NULL), history_out(NULL), + progress(NULL), tt_is_valid(false), master(-1), + slave(NULL), protect_slave_from_dying(-1), + direct_stdin(false) { dpkgbuf[0] = '\0'; - if(_config->FindB("Dpkg::Progress-Fancy", false) == true) - { - fancy_progress_output = true; - _config->Set("DpkgPM::Progress", true); - } + } + ~pkgDPkgPMPrivate() + { } bool stdin_is_dev_null; // the buffer we use for the dpkg status-fd reading char dpkgbuf[1024]; - int dpkgbuf_pos; + size_t dpkgbuf_pos; FILE *term_out; FILE *history_out; string dpkg_error; + APT::Progress::PackageManager *progress; - float last_reported_progress; - int nr_terminal_rows; - bool fancy_progress_output; + // pty stuff + struct termios tt; + bool tt_is_valid; + int master; + char * slave; + int protect_slave_from_dying; + + // signals + sigset_t sigmask; + sigset_t original_sigmask; + + bool direct_stdin; }; namespace @@ -100,7 +126,7 @@ namespace const char *target; public: - MatchProcessingOp(const char *the_target) + explicit MatchProcessingOp(const char *the_target) : target(the_target) { } @@ -137,21 +163,6 @@ ionice(int PID) return ExecWait(Process, "ionice"); } -// dpkgChrootDirectory - chrooting for dpkg if needed /*{{{*/ -static void dpkgChrootDirectory() -{ - std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory"); - if (chrootDir == "/") - return; - std::cerr << "Chrooting into " << chrootDir << std::endl; - if (chroot(chrootDir.c_str()) != 0) - _exit(100); - if (chdir("/") != 0) - _exit(100); -} - /*}}}*/ - - // FindNowVersion - Helper to find a Version in "now" state /*{{{*/ // --------------------------------------------------------------------- /* This is helpful when a package is no longer installed but has residual @@ -162,18 +173,12 @@ pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg) { pkgCache::VerIterator Ver; for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver) - { - pkgCache::VerFileIterator Vf = Ver.FileList(); - pkgCache::PkgFileIterator F = Vf.File(); - for (F = Vf.File(); F.end() == false; ++F) - { - if (F && F.Archive()) - { - if (strcmp(F.Archive(), "now")) - return Ver; - } - } - } + for (pkgCache::VerFileIterator Vf = Ver.FileList(); Vf.end() == false; ++Vf) + for (pkgCache::PkgFileIterator F = Vf.File(); F.end() == false; ++F) + { + if (F.Archive() != 0 && strcmp(F.Archive(), "now") == 0) + return Ver; + } return Ver; } /*}}}*/ @@ -181,10 +186,9 @@ pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg) // DPkgPM::pkgDPkgPM - Constructor /*{{{*/ // --------------------------------------------------------------------- /* */ -pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) - : pkgPackageManager(Cache), PackagesDone(0), PackagesTotal(0) +pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) + : pkgPackageManager(Cache),d(new pkgDPkgPMPrivate()), pkgFailures(0), PackagesDone(0), PackagesTotal(0) { - d = new pkgDPkgPMPrivate(); } /*}}}*/ // DPkgPM::pkgDPkgPM - Destructor /*{{{*/ @@ -375,10 +379,14 @@ bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version) that are due to be installed. */ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) { + bool result = true; + Configuration::Item const *Opts = _config->Tree(Cnf); if (Opts == 0 || Opts->Child == 0) return true; Opts = Opts->Child; + + sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN); unsigned int Count = 1; for (; Opts != 0; Opts = Opts->Next, Count++) @@ -386,6 +394,10 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) if (Opts->Value.empty() == true) continue; + if(_config->FindB("Debug::RunScripts", false) == true) + std::clog << "Running external script with list of all .deb file: '" + << Opts->Value << "'" << std::endl; + // Determine the protocol version string OptSec = Opts->Value; string::size_type Pos; @@ -397,17 +409,23 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO); // Create the pipes + std::set KeepFDs; + MergeKeepFdsFromConfiguration(KeepFDs); int Pipes[2]; - if (pipe(Pipes) != 0) - return _error->Errno("pipe","Failed to create IPC pipe to subprocess"); + if (pipe(Pipes) != 0) { + result = _error->Errno("pipe","Failed to create IPC pipe to subprocess"); + break; + } if (InfoFD != (unsigned)Pipes[0]) SetCloseExec(Pipes[0],true); else - _config->Set("APT::Keep-Fds::", Pipes[0]); + KeepFDs.insert(Pipes[0]); + + SetCloseExec(Pipes[1],true); // Purified Fork for running the script - pid_t Process = ExecFork(); + pid_t Process = ExecFork(KeepFDs); if (Process == 0) { // Setup the FDs @@ -420,7 +438,7 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) strprintf(hookfd, "%d", InfoFD); setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1); - dpkgChrootDirectory(); + debSystem::DpkgChrootDirectory(); const char *Args[4]; Args[0] = "/bin/sh"; Args[1] = "-c"; @@ -429,12 +447,12 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) execv(Args[0],(char **)Args); _exit(100); } - if (InfoFD == (unsigned)Pipes[0]) - _config->Clear("APT::Keep-Fds", Pipes[0]); close(Pipes[0]); FILE *F = fdopen(Pipes[1],"w"); - if (F == 0) - return _error->Errno("fdopen","Faild to open new FD"); + if (F == 0) { + result = _error->Errno("fdopen","Faild to open new FD"); + break; + } // Feed it the filenames. if (Version <= 1) @@ -462,21 +480,24 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) fclose(F); // Clean up the sub process - if (ExecWait(Process,Opts->Value.c_str()) == false) - return _error->Error("Failure running script %s",Opts->Value.c_str()); + if (ExecWait(Process,Opts->Value.c_str()) == false) { + result = _error->Error("Failure running script %s",Opts->Value.c_str()); + break; + } } + signal(SIGPIPE, old_sigpipe); - return true; + return result; } /*}}}*/ -// DPkgPM::DoStdin - Read stdin and pass to slave pty /*{{{*/ +// DPkgPM::DoStdin - Read stdin and pass to master pty /*{{{*/ // --------------------------------------------------------------------- /* */ void pkgDPkgPM::DoStdin(int master) { unsigned char input_buf[256] = {0,}; - ssize_t len = read(0, input_buf, sizeof(input_buf)); + ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf)); if (len) FileFd::Write(master, input_buf, len); else @@ -513,159 +534,187 @@ void pkgDPkgPM::DoTerminalPty(int master) // --------------------------------------------------------------------- /* */ -void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line) +void pkgDPkgPM::ProcessDpkgStatusLine(char *line) { bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false); - // the status we output - ostringstream status; - if (Debug == true) std::clog << "got from dpkg '" << line << "'" << std::endl; - /* dpkg sends strings like this: 'status: : ' 'status: :: ' - errors look like this: - '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 - and conffile-prompt like this - 'status: conffile-prompt: conffile : 'current-conffile' 'new-conffile' useredited distedited - Newer versions of dpkg sent also: - 'processing: install: pkg' - 'processing: configure: pkg' - 'processing: remove: pkg' - 'processing: purge: pkg' - 'processing: disappear: pkg' - 'processing: trigproc: trigger' - + 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: pkg' + 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: trigger' */ + // we need to split on ": " (note the appended space) as the ':' is // part of the pkgname:arch information that dpkg sends // // A dpkg error message may contain additional ":" (like // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..." // so we need to ensure to not split too much - std::vector list = StringSplit(line, ": ", 3); - if(list.size() != 3) + std::vector list = StringSplit(line, ": ", 4); + if(list.size() < 3) { if (Debug == true) std::clog << "ignoring line: not enough ':'" << std::endl; return; } - // dpkg does not send always send "pkgname:arch" so we add it here if needed - std::string pkgname = list[1]; + + // build the (prefix, pkgname, action) tuple, position of this + // is different for "processing" or "status" messages + std::string prefix = APT::String::Strip(list[0]); + std::string pkgname; + std::string action; + + // "processing" has the form "processing: action: pkg or trigger" + // with action = ["install", "upgrade", "configure", "remove", "purge", + // "disappear", "trigproc"] + if (prefix == "processing") + { + pkgname = APT::String::Strip(list[2]); + action = APT::String::Strip(list[1]); + // we don't care for the difference (as dpkg doesn't really either) + if (action == "upgrade") + action = "install"; + } + // "status" has the form: "status: pkg: state" + // with state in ["half-installed", "unpacked", "half-configured", + // "installed", "config-files", "not-installed"] + else if (prefix == "status") + { + pkgname = APT::String::Strip(list[1]); + action = APT::String::Strip(list[2]); + } else { + if (Debug == true) + std::clog << "unknown prefix '" << prefix << "'" << std::endl; + return; + } + + + /* handle the special cases first: + + errors look like this: + '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 + and conffile-prompt like this + 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited + */ + if (prefix == "status") + { + if(action == "error") + { + d->progress->Error(pkgname, PackagesDone, PackagesTotal, + list[3]); + pkgFailures++; + WriteApportReport(pkgname.c_str(), list[3].c_str()); + return; + } + else if(action == "conffile-prompt") + { + d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, + list[3]); + return; + } + } + + // at this point we know that we should have a valid pkgname, so build all + // the info from it + + // dpkg does not always send "pkgname:arch" so we add it here if needed if (pkgname.find(":") == std::string::npos) { - string const nativeArch = _config->Find("APT::Architecture"); - pkgname = pkgname + ":" + nativeArch; + // find the package in the group that is touched by dpkg + // if there are multiple pkgs dpkg would send us a full pkgname:arch + pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname); + if (Grp.end() == false) + { + pkgCache::PkgIterator P = Grp.PackageList(); + for (; P.end() != true; P = Grp.NextPkg(P)) + { + if(Cache[P].Keep() == false || Cache[P].ReInstall() == true) + { + pkgname = P.FullName(); + break; + } + } + } } + const char* const pkg = pkgname.c_str(); - const char* action = list[2].c_str(); + std::string short_pkgname = StringSplit(pkgname, ":")[0]; + std::string arch = ""; + if (pkgname.find(":") != string::npos) + arch = StringSplit(pkgname, ":")[1]; + std::string i18n_pkgname = pkgname; + if (arch.size() != 0) + strprintf(i18n_pkgname, "%s (%s)", short_pkgname.c_str(), arch.c_str()); // 'processing' from dpkg looks like // 'processing: action: pkg' - if(strncmp(list[0].c_str(), "processing", strlen("processing")) == 0) + if(prefix == "processing") { - char s[200]; - const char* const pkg_or_trigger = list[2].c_str(); - action = list[1].c_str(); const std::pair * const iter = std::find_if(PackageProcessingOpsBegin, PackageProcessingOpsEnd, - MatchProcessingOp(action)); + MatchProcessingOp(action.c_str())); if(iter == PackageProcessingOpsEnd) { if (Debug == true) std::clog << "ignoring unknown action: " << action << std::endl; return; } - snprintf(s, sizeof(s), _(iter->second), pkg_or_trigger); - - status << "pmstatus:" << pkg_or_trigger - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << s - << endl; - if(OutStatusFd > 0) - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; - - if (strncmp(action, "disappear", strlen("disappear")) == 0) - handleDisappearAction(pkg_or_trigger); + std::string msg; + strprintf(msg, _(iter->second), i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); + + // FIXME: this needs a muliarch testcase + // FIXME2: is "pkgname" here reliable with dpkg only sending us + // short pkgnames? + if (action == "disappear") + handleDisappearAction(pkgname); return; - } + } - if(strncmp(action,"error",strlen("error")) == 0) - { - status << "pmerror:" << list[1] - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << list[3] - << endl; - if(OutStatusFd > 0) - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; - pkgFailures++; - WriteApportReport(list[1].c_str(), list[3].c_str()); - return; - } - else if(strncmp(action,"conffile",strlen("conffile")) == 0) + if (prefix == "status") { - status << "pmconffile:" << list[1] - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << list[3] - << endl; - if(OutStatusFd > 0) - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; - return; - } + vector const &states = PackageOps[pkg]; + if(PackageOpsDone[pkg] < states.size()) + { + char const * const next_action = states[PackageOpsDone[pkg]].state; + if (next_action && Debug == true) + std::clog << "(parsed from dpkg) pkg: " << short_pkgname + << " action: " << action << " (expected: '" << next_action << "' " + << PackageOpsDone[pkg] << " of " << states.size() << ")" << endl; + + // check if the package moved to the next dpkg state + if(next_action && (action == next_action)) + { + // only read the translation if there is actually a next action + char const * const translation = _(states[PackageOpsDone[pkg]].str); - vector const &states = PackageOps[pkg]; - const char *next_action = NULL; - if(PackageOpsDone[pkg] < states.size()) - next_action = states[PackageOpsDone[pkg]].state; - // check if the package moved to the next dpkg state - if(next_action && (strcmp(action, next_action) == 0)) - { - // only read the translation if there is actually a next - // action - const char *translation = _(states[PackageOpsDone[pkg]].str); - char s[200]; - snprintf(s, sizeof(s), translation, pkg); - - // we moved from one dpkg state to a new one, report that - PackageOpsDone[pkg]++; - PackagesDone++; - // build the status str - status << "pmstatus:" << pkg - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << s - << endl; - if(_config->FindB("DPkgPM::Progress", false) == true) - SendTerminalProgress(PackagesDone/float(PackagesTotal)*100.0); - - if(OutStatusFd > 0) - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; + // we moved from one dpkg state to a new one, report that + ++PackageOpsDone[pkg]; + ++PackagesDone; + + std::string msg; + strprintf(msg, translation, i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); + } + } } - if (Debug == true) - std::clog << "(parsed from dpkg) pkg: " << pkg - << " action: " << action << endl; } /*}}}*/ // DPkgPM::handleDisappearAction /*{{{*/ void pkgDPkgPM::handleDisappearAction(string const &pkgname) { - // record the package name for display and stuff later - disappearedPkgs.insert(pkgname); - pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); if (unlikely(Pkg.end() == true)) return; + + // record the package name for display and stuff later + disappearedPkgs.insert(Pkg.FullName(true)); + // the disappeared package was auto-installed - nothing to do if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) return; @@ -705,40 +754,33 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname) } /*}}}*/ // DPkgPM::DoDpkgStatusFd /*{{{*/ -// --------------------------------------------------------------------- -/* - */ -void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) +void pkgDPkgPM::DoDpkgStatusFd(int statusfd) { - char *p, *q; - int len; - - len=read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos], sizeof(d->dpkgbuf)-d->dpkgbuf_pos); - d->dpkgbuf_pos += len; + ssize_t const len = read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos], + (sizeof(d->dpkgbuf)/sizeof(d->dpkgbuf[0])) - d->dpkgbuf_pos); if(len <= 0) return; + d->dpkgbuf_pos += (len / sizeof(d->dpkgbuf[0])); - // process line by line if we have a buffer - p = q = d->dpkgbuf; - while((q=(char*)memchr(p, '\n', d->dpkgbuf+d->dpkgbuf_pos-p)) != NULL) + // process line by line from the buffer + char *p = d->dpkgbuf, *q = nullptr; + while((q=(char*)memchr(p, '\n', (d->dpkgbuf + d->dpkgbuf_pos) - p)) != nullptr) { - *q = 0; - ProcessDpkgStatusLine(OutStatusFd, p); - p=q+1; // continue with next line + *q = '\0'; + ProcessDpkgStatusLine(p); + p = q + 1; // continue with next line } - // now move the unprocessed bits (after the final \n that is now a 0x0) - // to the start and update d->dpkgbuf_pos - p = (char*)memrchr(d->dpkgbuf, 0, d->dpkgbuf_pos); - if(p == NULL) + // check if we stripped the buffer clean + if (p > (d->dpkgbuf + d->dpkgbuf_pos)) + { + d->dpkgbuf_pos = 0; return; + } - // we are interessted in the first char *after* 0x0 - p++; - - // move the unprocessed tail to the start and update pos - memmove(d->dpkgbuf, p, p-d->dpkgbuf); - d->dpkgbuf_pos = d->dpkgbuf+d->dpkgbuf_pos-p; + // otherwise move the unprocessed tail to the start and update pos + memmove(d->dpkgbuf, p, (p - d->dpkgbuf)); + d->dpkgbuf_pos = (d->dpkgbuf + d->dpkgbuf_pos) - p; } /*}}}*/ // DPkgPM::WriteHistoryTag /*{{{*/ @@ -763,7 +805,8 @@ bool pkgDPkgPM::OpenLog() // get current time char timestr[200]; time_t const t = time(NULL); - struct tm const * const tmp = localtime(&t); + struct tm tm_buf; + struct tm const * const tmp = localtime_r(&t, &tm_buf); strftime(timestr, sizeof(timestr), "%F %T", tmp); // open terminal log @@ -850,7 +893,8 @@ bool pkgDPkgPM::CloseLog() { char timestr[200]; time_t t = time(NULL); - struct tm *tmp = localtime(&t); + struct tm tm_buf; + struct tm *tmp = localtime_r(&t, &tm_buf); strftime(timestr, sizeof(timestr), "%F %T", tmp); if(d->term_out) @@ -889,50 +933,6 @@ bool pkgDPkgPM::CloseLog() return true; } /*}}}*/ -// DPkgPM::SendTerminalProgress /*{{{*/ -// --------------------------------------------------------------------- -/* Send progress info to the terminal - */ -void pkgDPkgPM::SendTerminalProgress(float percentage) -{ - int reporting_steps = _config->FindI("DpkgPM::Reporting-Steps", 1); - - if(percentage < (d->last_reported_progress + reporting_steps)) - return; - - std::string progress_str; - strprintf(progress_str, "Progress: [%3i%%]", (int)percentage); - if (d->fancy_progress_output) - { - int row = d->nr_terminal_rows; - - static string save_cursor = "\033[s"; - static string restore_cursor = "\033[u"; - - static string set_bg_color = "\033[42m"; // green - static string set_fg_color = "\033[30m"; // black - - static string restore_bg = "\033[49m"; - static string restore_fg = "\033[39m"; - - std::cout << save_cursor - // move cursor position to last row - << "\033[" << row << ";0f" - << set_bg_color - << set_fg_color - << progress_str - << restore_cursor - << restore_bg - << restore_fg; - } - else - { - std::cout << progress_str << "\r\n"; - } - std::flush(std::cout); - - d->last_reported_progress = percentage; -} /*}}}*/ /*{{{*/ // This implements a racy version of pselect for those architectures @@ -954,115 +954,11 @@ static int racy_pselect(int nfds, fd_set *readfds, fd_set *writefds, sigprocmask(SIG_SETMASK, &origmask, 0); return retval; } -/*}}}*/ - -void pkgDPkgPM::SetupTerminalScrollArea(int nr_rows) -{ - if(!d->fancy_progress_output) - return; - - // scroll down a bit to avoid visual glitch when the screen - // area shrinks by one row - std::cout << "\n"; - - // save cursor - std::cout << "\033[s"; - - // set scroll region (this will place the cursor in the top left) - std::cout << "\033[1;" << nr_rows - 1 << "r"; - - // restore cursor but ensure its inside the scrolling area - std::cout << "\033[u"; - static const char *move_cursor_up = "\033[1A"; - std::cout << move_cursor_up; - std::flush(std::cout); -} + /*}}}*/ -// DPkgPM::Go - Run the sequence /*{{{*/ -// --------------------------------------------------------------------- -/* This globs the operations and calls dpkg - * - * If it is called with "OutStatusFd" set to a valid file descriptor - * apt will report the install progress over this fd. It maps the - * dpkg states a package goes through to human readable (and i10n-able) - * names and calculates a percentage for each step. -*/ -bool pkgDPkgPM::Go(int OutStatusFd) +// DPkgPM::BuildPackagesProgressMap /*{{{*/ +void pkgDPkgPM::BuildPackagesProgressMap() { - pkgPackageManager::SigINTStop = false; - - // Generate the base argument list for dpkg - std::vector Args; - unsigned long StartSize = 0; - string Tmp = _config->Find("Dir::Bin::dpkg","dpkg"); - { - string const dpkgChrootDir = _config->FindDir("DPkg::Chroot-Directory", "/"); - size_t dpkgChrootLen = dpkgChrootDir.length(); - if (dpkgChrootDir != "/" && Tmp.find(dpkgChrootDir) == 0) - { - if (dpkgChrootDir[dpkgChrootLen - 1] == '/') - --dpkgChrootLen; - Tmp = Tmp.substr(dpkgChrootLen); - } - } - Args.push_back(Tmp.c_str()); - StartSize += Tmp.length(); - - // Stick in any custom dpkg options - Configuration::Item const *Opts = _config->Tree("DPkg::Options"); - if (Opts != 0) - { - Opts = Opts->Child; - for (; Opts != 0; Opts = Opts->Next) - { - if (Opts->Value.empty() == true) - continue; - Args.push_back(Opts->Value.c_str()); - StartSize += Opts->Value.length(); - } - } - - size_t const BaseArgs = Args.size(); - // we need to detect if we can qualify packages with the architecture or not - Args.push_back("--assert-multi-arch"); - Args.push_back(NULL); - - pid_t dpkgAssertMultiArch = ExecFork(); - if (dpkgAssertMultiArch == 0) - { - dpkgChrootDirectory(); - // redirect everything to the ultimate sink as we only need the exit-status - int const nullfd = open("/dev/null", O_RDONLY); - dup2(nullfd, STDIN_FILENO); - dup2(nullfd, STDOUT_FILENO); - dup2(nullfd, STDERR_FILENO); - execvp(Args[0], (char**) &Args[0]); - _error->WarningE("dpkgGo", "Can't detect if dpkg supports multi-arch!"); - _exit(2); - } - - fd_set rfds; - struct timespec tv; - sigset_t sigmask; - sigset_t original_sigmask; - - unsigned int const MaxArgs = _config->FindI("Dpkg::MaxArgs",8*1024); - unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",32*1024); - bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false); - - if (RunScripts("DPkg::Pre-Invoke") == false) - return false; - - if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false) - return false; - - // support subpressing of triggers processing for special - // cases like d-i that runs the triggers handling manually - bool const SmartConf = (_config->Find("PackageManager::Configure", "all") != "all"); - bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false); - if (_config->FindB("DPkg::ConfigurePending", SmartConf) == true) - List.push_back(Item(Item::ConfigurePending, PkgIterator())); - // map the dpkg states to the operations that are performed // (this is sorted in the same way as Item::Ops) static const struct DpkgState DpkgStatesOpMap[][7] = { @@ -1111,29 +1007,242 @@ bool pkgDPkgPM::Go(int OutStatusFd) PackagesTotal++; } } + /* one extra: We don't want the progress bar to reach 100%, especially not + if we call dpkg --configure --pending and process a bunch of triggers + while showing 100%. Also, spindown takes a while, so never reaching 100% + is way more correct than reaching 100% while still doing stuff even if + doing it this way is slightly bending the rules */ + ++PackagesTotal; +} + /*}}}*/ +bool pkgDPkgPM::Go(int StatusFd) +{ + APT::Progress::PackageManager *progress = NULL; + if (StatusFd == -1) + progress = APT::Progress::PackageManagerProgressFactory(); + else + progress = new APT::Progress::PackageManagerProgressFd(StatusFd); + + return Go(progress); +} - d->stdin_is_dev_null = false; +void pkgDPkgPM::StartPtyMagic() +{ + if (_config->FindB("Dpkg::Use-Pty", true) == false) + { + d->master = -1; + if (d->slave != NULL) + free(d->slave); + d->slave = NULL; + return; + } - // create log - OpenLog(); + if (isatty(STDIN_FILENO) == 0) + d->direct_stdin = true; + + _error->PushToStack(); - bool dpkgMultiArch = false; - if (dpkgAssertMultiArch > 0) + d->master = posix_openpt(O_RDWR | O_NOCTTY); + if (d->master == -1) + _error->Errno("posix_openpt", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); + else if (unlockpt(d->master) == -1) + _error->Errno("unlockpt", "Unlocking the slave of master fd %d failed!", d->master); + else { - int Status = 0; - while (waitpid(dpkgAssertMultiArch, &Status, 0) != dpkgAssertMultiArch) +#ifdef HAVE_PTS_NAME_R + char slave_name[64]; // 64 is used by bionic + if (ptsname_r(d->master, slave_name, sizeof(slave_name)) != 0) +#else + char const * const slave_name = ptsname(d->master); + if (slave_name == NULL) +#endif + _error->Errno("ptsname", "Getting name for slave of master fd %d failed!", d->master); + else { - if (errno == EINTR) - continue; - _error->WarningE("dpkgGo", _("Waited for %s but it wasn't there"), "dpkg --assert-multi-arch"); - break; + d->slave = strdup(slave_name); + if (d->slave == NULL) + _error->Errno("strdup", "Copying name %s for slave of master fd %d failed!", slave_name, d->master); + else if (grantpt(d->master) == -1) + _error->Errno("grantpt", "Granting access to slave %s based on master fd %d failed!", slave_name, d->master); + else if (tcgetattr(STDIN_FILENO, &d->tt) == 0) + { + d->tt_is_valid = true; + struct termios raw_tt; + // copy window size of stdout if its a 'good' terminal + if (tcgetattr(STDOUT_FILENO, &raw_tt) == 0) + { + struct winsize win; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0) + _error->Errno("ioctl", "Getting TIOCGWINSZ from stdout failed!"); + if (ioctl(d->master, TIOCSWINSZ, &win) < 0) + _error->Errno("ioctl", "Setting TIOCSWINSZ for master fd %d failed!", d->master); + } + if (tcsetattr(d->master, TCSANOW, &d->tt) == -1) + _error->Errno("tcsetattr", "Setting in Start via TCSANOW for master fd %d failed!", d->master); + + raw_tt = d->tt; + cfmakeraw(&raw_tt); + raw_tt.c_lflag &= ~ECHO; + raw_tt.c_lflag |= ISIG; + // block SIGTTOU during tcsetattr to prevent a hang if + // the process is a member of the background process group + // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html + sigemptyset(&d->sigmask); + sigaddset(&d->sigmask, SIGTTOU); + sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_tt) == -1) + _error->Errno("tcsetattr", "Setting in Start via TCSAFLUSH for stdin failed!"); + sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL); + + } + if (d->slave != NULL) + { + /* on linux, closing (and later reopening) all references to the slave + makes the slave a death end, so we open it here to have one open all + the time. We could use this fd in SetupSlavePtyMagic() for linux, but + on kfreebsd we get an incorrect ("step like") output then while it has + no problem with closing all references… so to avoid platform specific + code here we combine both and be happy once more */ + d->protect_slave_from_dying = open(d->slave, O_RDWR | O_CLOEXEC | O_NOCTTY); + } } - if (WIFEXITED(Status) == true && WEXITSTATUS(Status) == 0) - dpkgMultiArch = true; } - // this loop is runs once per operation - for (vector::const_iterator I = List.begin(); I != List.end();) + if (_error->PendingError() == true) + { + if (d->master != -1) + { + close(d->master); + d->master = -1; + } + if (d->slave != NULL) + { + free(d->slave); + d->slave = NULL; + } + _error->DumpErrors(std::cerr, GlobalError::DEBUG, false); + } + _error->RevertToStack(); +} +void pkgDPkgPM::SetupSlavePtyMagic() +{ + if(d->master == -1 || d->slave == NULL) + return; + + if (close(d->master) == -1) + _error->FatalE("close", "Closing master %d in child failed!", d->master); + d->master = -1; + if (setsid() == -1) + _error->FatalE("setsid", "Starting a new session for child failed!"); + + int const slaveFd = open(d->slave, O_RDWR | O_NOCTTY); + if (slaveFd == -1) + _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); + else if (ioctl(slaveFd, TIOCSCTTY, 0) < 0) + _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd); + else + { + unsigned short i = 0; + if (d->direct_stdin == true) + ++i; + for (; i < 3; ++i) + if (dup2(slaveFd, i) == -1) + _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i); + + if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSANOW, &d->tt) < 0) + _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd); + } + + if (slaveFd != -1) + close(slaveFd); +} +void pkgDPkgPM::StopPtyMagic() +{ + if (d->slave != NULL) + free(d->slave); + d->slave = NULL; + if (d->protect_slave_from_dying != -1) + { + close(d->protect_slave_from_dying); + d->protect_slave_from_dying = -1; + } + if(d->master >= 0) + { + if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSAFLUSH, &d->tt) == -1) + _error->FatalE("tcsetattr", "Setting in Stop via TCSAFLUSH for stdin failed!"); + close(d->master); + d->master = -1; + } +} + +// DPkgPM::Go - Run the sequence /*{{{*/ +// --------------------------------------------------------------------- +/* This globs the operations and calls dpkg + * + * If it is called with a progress object apt will report the install + * progress to this object. It maps the dpkg states a package goes + * through to human readable (and i10n-able) + * names and calculates a percentage for each step. + */ +bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) +{ + pkgPackageManager::SigINTStop = false; + d->progress = progress; + + // Generate the base argument list for dpkg + std::vector const sArgs = debSystem::GetDpkgBaseCommand(); + std::vector Args(sArgs.size(), NULL); + std::transform(sArgs.begin(), sArgs.end(), Args.begin(), + [](std::string const &s) { return s.c_str(); }); + unsigned long long const StartSize = std::accumulate(sArgs.begin(), sArgs.end(), 0, + [](unsigned long long const i, std::string const &s) { return i + s.length(); }); + size_t const BaseArgs = Args.size(); + + fd_set rfds; + struct timespec tv; + + // FIXME: do we really need this limit when we have MaxArgBytes? + unsigned int const MaxArgs = _config->FindI("Dpkg::MaxArgs",32*1024); + + // try to figure out the max environment size + int OSArgMax = sysconf(_SC_ARG_MAX); + if(OSArgMax < 0) + OSArgMax = 32*1024; + OSArgMax -= EnvironmentSize() - 2*1024; + unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax); + bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false); + + if (RunScripts("DPkg::Pre-Invoke") == false) + return false; + + if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false) + return false; + + // support subpressing of triggers processing for special + // cases like d-i that runs the triggers handling manually + bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false); + if (_config->FindB("DPkg::ConfigurePending", true) == true) + List.push_back(Item(Item::ConfigurePending, PkgIterator())); + + // for the progress + BuildPackagesProgressMap(); + + d->stdin_is_dev_null = false; + + // create log + OpenLog(); + + bool dpkgMultiArch = debSystem::SupportsMultiArch(); + + // start pty magic before the loop + StartPtyMagic(); + + // Tell the progress that its starting and fork dpkg + d->progress->Start(d->master); + + // this loop is runs once per dpkg operation + vector::const_iterator I = List.begin(); + while (I != List.end()) { // Do all actions with the same Op in one run vector::const_iterator J = I; @@ -1254,7 +1363,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) { if((*I).Pkg.end() == true) continue; - if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.Name()) != disappearedPkgs.end()) + if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.FullName(true)) != disappearedPkgs.end()) continue; // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch || @@ -1301,6 +1410,10 @@ bool pkgDPkgPM::Go(int OutStatusFd) a != Args.end(); ++a) clog << *a << ' '; clog << endl; + for (std::vector::const_iterator p = Packages.begin(); + p != Packages.end(); ++p) + free(*p); + Packages.clear(); continue; } Args.push_back(NULL); @@ -1324,85 +1437,27 @@ bool pkgDPkgPM::Go(int OutStatusFd) // ignore SIGHUP as well (debian #463030) sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN); - struct termios tt; - struct winsize win; - int master = -1; - int slave = -1; - - // if tcgetattr does not return zero there was a error - // and we do not do any pty magic - _error->PushToStack(); - if (tcgetattr(STDOUT_FILENO, &tt) == 0) - { - ioctl(1, TIOCGWINSZ, (char *)&win); - d->nr_terminal_rows = win.ws_row; - if (openpty(&master, &slave, NULL, &tt, &win) < 0) - { - _error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); - master = slave = -1; - } else { - struct termios rtt; - rtt = tt; - cfmakeraw(&rtt); - rtt.c_lflag &= ~ECHO; - rtt.c_lflag |= ISIG; - // block SIGTTOU during tcsetattr to prevent a hang if - // the process is a member of the background process group - // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html - sigemptyset(&sigmask); - sigaddset(&sigmask, SIGTTOU); - sigprocmask(SIG_BLOCK,&sigmask, &original_sigmask); - tcsetattr(0, TCSAFLUSH, &rtt); - sigprocmask(SIG_SETMASK, &original_sigmask, 0); - } - } - // complain only if stdout is either a terminal (but still failed) or is an invalid - // descriptor otherwise we would complain about redirection to e.g. /dev/null as well. - else if (isatty(STDOUT_FILENO) == 1 || errno == EBADF) - _error->Errno("tcgetattr", _("Can not write log (%s)"), _("Is stdout a terminal?")); - - if (_error->PendingError() == true) - _error->DumpErrors(std::cerr); - _error->RevertToStack(); - - // Fork dpkg - pid_t Child; - _config->Set("APT::Keep-Fds::",fd[1]); - // send status information that we are about to fork dpkg - if(OutStatusFd > 0) { - ostringstream status; - status << "pmstatus:dpkg-exec:" - << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << _("Running dpkg") - << endl; - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - } - - Child = ExecFork(); - // This is the child + // now run dpkg + d->progress->StartDpkg(); + std::set KeepFDs; + KeepFDs.insert(fd[1]); + MergeKeepFdsFromConfiguration(KeepFDs); + pid_t Child = ExecFork(KeepFDs); if (Child == 0) { - - if(slave >= 0 && master >= 0) - { - setsid(); - ioctl(slave, TIOCSCTTY, 0); - close(master); - dup2(slave, 0); - dup2(slave, 1); - dup2(slave, 2); - close(slave); - } + // This is the child + SetupSlavePtyMagic(); close(fd[0]); // close the read end of the pipe - dpkgChrootDirectory(); + debSystem::DpkgChrootDirectory(); if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0) _exit(100); if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO)) { - int Flags,dummy; + int Flags; + int dummy = 0; if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0) _exit(100); @@ -1415,11 +1470,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0) _exit(100); } - SetupTerminalScrollArea(d->nr_terminal_rows); - /* No Job Control Stop Env is a magic dpkg var that prevents it - from using sigstop */ - putenv((char *)"DPKG_NO_TSTP=yes"); execvp(Args[0], (char**) &Args[0]); cerr << "Could not exec dpkg!" << endl; _exit(100); @@ -1429,9 +1480,6 @@ bool pkgDPkgPM::Go(int OutStatusFd) if (_config->FindB("DPkg::UseIoNice", false) == true) ionice(Child); - // clear the Keep-Fd again - _config->Clear("APT::Keep-Fds",fd[1]); - // Wait for dpkg int Status = 0; @@ -1439,12 +1487,9 @@ bool pkgDPkgPM::Go(int OutStatusFd) int const _dpkgin = fd[0]; close(fd[1]); // close the write end of the pipe - if(slave > 0) - close(slave); - // setups fds - sigemptyset(&sigmask); - sigprocmask(SIG_BLOCK,&sigmask,&original_sigmask); + sigemptyset(&d->sigmask); + sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask); /* free vectors (and therefore memory) as we don't need the included data anymore */ for (std::vector::const_iterator p = Packages.begin(); @@ -1473,18 +1518,19 @@ bool pkgDPkgPM::Go(int OutStatusFd) // wait for input or output here FD_ZERO(&rfds); - if (master >= 0 && !d->stdin_is_dev_null) - FD_SET(0, &rfds); + if (d->master >= 0 && d->direct_stdin == false && d->stdin_is_dev_null == false) + FD_SET(STDIN_FILENO, &rfds); FD_SET(_dpkgin, &rfds); - if(master >= 0) - FD_SET(master, &rfds); - tv.tv_sec = 1; - tv.tv_nsec = 0; - select_ret = pselect(max(master, _dpkgin)+1, &rfds, NULL, NULL, - &tv, &original_sigmask); + if(d->master >= 0) + FD_SET(d->master, &rfds); + tv.tv_sec = 0; + tv.tv_nsec = d->progress->GetPulseInterval(); + select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL, + &tv, &d->original_sigmask); if (select_ret < 0 && (errno == EINVAL || errno == ENOSYS)) - select_ret = racy_pselect(max(master, _dpkgin)+1, &rfds, NULL, - NULL, &tv, &original_sigmask); + select_ret = racy_pselect(max(d->master, _dpkgin)+1, &rfds, NULL, + NULL, &tv, &d->original_sigmask); + d->progress->Pulse(); if (select_ret == 0) continue; else if (select_ret < 0 && errno == EINTR) @@ -1495,12 +1541,12 @@ bool pkgDPkgPM::Go(int OutStatusFd) continue; } - if(master >= 0 && FD_ISSET(master, &rfds)) - DoTerminalPty(master); - if(master >= 0 && FD_ISSET(0, &rfds)) - DoStdin(master); + if(d->master >= 0 && FD_ISSET(d->master, &rfds)) + DoTerminalPty(d->master); + if(d->master >= 0 && FD_ISSET(0, &rfds)) + DoStdin(d->master); if(FD_ISSET(_dpkgin, &rfds)) - DoDpkgStatusFd(_dpkgin, OutStatusFd); + DoDpkgStatusFd(_dpkgin); } close(_dpkgin); @@ -1509,22 +1555,6 @@ bool pkgDPkgPM::Go(int OutStatusFd) signal(SIGINT,old_SIGINT); signal(SIGHUP,old_SIGHUP); - - // reset scroll area - SetupTerminalScrollArea(d->nr_terminal_rows + 1); - if(d->fancy_progress_output) - { - // override the progress line (sledgehammer) - static const char* clear_screen_below_cursor = "\033[J"; - std::cout << clear_screen_below_cursor; - } - - if(master >= 0) - { - tcsetattr(0, TCSAFLUSH, &tt); - close(master); - } - // Check for an error code. if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) { @@ -1532,32 +1562,23 @@ bool pkgDPkgPM::Go(int OutStatusFd) // here but keep the loop going and just report it as a error // for later bool const stopOnError = _config->FindB("Dpkg::StopOnError",true); - - if(stopOnError) - RunScripts("DPkg::Post-Invoke"); - if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) + if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]); else if (WIFEXITED(Status) != 0) strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); - else + else strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]); + _error->Error("%s", d->dpkg_error.c_str()); - if(d->dpkg_error.size() > 0) - _error->Error("%s", d->dpkg_error.c_str()); - - if(stopOnError) - { - CloseLog(); - return false; - } - } + if(stopOnError) + break; + } } - CloseLog(); - // dpkg is done at this point - if(_config->FindB("DPkgPM::Progress", false) == true) - SendTerminalProgress(100); + d->progress->Stop(); + StopPtyMagic(); + CloseLog(); if (pkgPackageManager::SigINTStop) _error->Warning(_("Operation was interrupted before it could finish")); @@ -1569,7 +1590,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) { std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache"); if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true && - unlink(oldpkgcache.c_str()) == 0) + RemoveFile("pkgDPkgPM::Go", oldpkgcache)) { std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache"); if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true) @@ -1583,10 +1604,10 @@ bool pkgDPkgPM::Go(int OutStatusFd) } Cache.writeStateFile(NULL); - return true; + return d->dpkg_error.empty(); } -void SigINT(int sig) { +void SigINT(int /*sig*/) { pkgPackageManager::SigINTStop = true; } /*}}}*/ @@ -1609,11 +1630,11 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) if (apportPkg.end() == true || apportPkg->CurrentVer == 0) return; - string pkgname, reportfile, srcpkgname, pkgver, arch; + string pkgname, reportfile, pkgver, arch; string::size_type pos; FILE *report; - if (_config->FindB("Dpkg::ApportFailureReport", false) == false) + if (_config->FindB("Dpkg::ApportFailureReport", true) == false) { std::clog << "configured to not write apport reports" << std::endl; return; @@ -1640,37 +1661,64 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) } // do not report out-of-memory failures - if(strstr(errormsg, strerror(ENOMEM)) != NULL) { + if(strstr(errormsg, strerror(ENOMEM)) != NULL || + strstr(errormsg, "failed to allocate memory") != NULL) { std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl; return; } - // do not report dpkg I/O errors - // XXX - this message is localized, but this only matches the English version. This is better than nothing. - if(strstr(errormsg, "short read in buffer_copy (")) { - std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl; + // do not report bugs regarding inaccessible local files + if(strstr(errormsg, strerror(ENOENT)) != NULL || + strstr(errormsg, "cannot access archive") != NULL) { + std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl; return; } + // do not report errors encountered when decompressing packages + if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) { + std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl; + return; + } + + // do not report dpkg I/O errors, this is a format string, so we compare + // the prefix and the suffix of the error with the dpkg error message + vector io_errors; + io_errors.push_back(string("failed to read")); + io_errors.push_back(string("failed to write")); + io_errors.push_back(string("failed to seek")); + io_errors.push_back(string("unexpected end of file or stream")); + + for (vector::iterator I = io_errors.begin(); I != io_errors.end(); ++I) + { + vector list = VectorizeString(dgettext("dpkg", (*I).c_str()), '%'); + if (list.size() > 1) { + // we need to split %s, VectorizeString only allows char so we need + // to kill the "s" manually + if (list[1].size() > 1) { + list[1].erase(0, 1); + if(strstr(errormsg, list[0].c_str()) && + strstr(errormsg, list[1].c_str())) { + std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl; + return; + } + } + } + } + // get the pkgname and reportfile pkgname = flNotDir(pkgpath); pos = pkgname.find('_'); if(pos != string::npos) pkgname = pkgname.substr(0, pos); - // find the package versin and source package name + // find the package version and source package name pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); if (Pkg.end() == true) return; - pkgCache::VerIterator Ver = Cache.GetCandidateVer(Pkg); + pkgCache::VerIterator Ver = Cache.GetCandidateVersion(Pkg); if (Ver.end() == true) return; pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr(); - pkgRecords Recs(Cache); - pkgRecords::Parser &Parse = Recs.Lookup(Ver.FileList()); - srcpkgname = Parse.SourcePkg(); - if(srcpkgname.empty()) - srcpkgname = pkgname; // if the file exists already, we check: // - if it was reported already (touched by apport). @@ -1719,9 +1767,10 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) fprintf(report, "ProblemType: Package\n"); fprintf(report, "Architecture: %s\n", arch.c_str()); time_t now = time(NULL); - fprintf(report, "Date: %s" , ctime(&now)); + char ctime_buf[26]; // need at least 26 bytes according to ctime(3) + fprintf(report, "Date: %s" , ctime_r(&now, ctime_buf)); fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str()); - fprintf(report, "SourcePackage: %s\n", srcpkgname.c_str()); + fprintf(report, "SourcePackage: %s\n", Ver.SourcePkgName()); fprintf(report, "ErrorMessage:\n %s\n", errormsg); // ensure that the log is flushed @@ -1737,6 +1786,22 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) fprintf(report, "DpkgTerminalLog:\n"); log = fopen(logfile_name.c_str(),"r"); if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + fprintf(report, " \n"); + fclose(log); + } + } + + // attach history log it if we have it + string histfile_name = _config->FindFile("Dir::Log::History"); + if (!histfile_name.empty()) + { + fprintf(report, "DpkgHistoryLog:\n"); + FILE* log = fopen(histfile_name.c_str(),"r"); + if(log != NULL) { char buf[1024]; while( fgets(buf, sizeof(buf), log) != NULL) @@ -1745,8 +1810,15 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) } } - // log the ordering - const char *ops_str[] = {"Install", "Configure","Remove","Purge"}; + // log the ordering, see dpkgpm.h and the "Ops" enum there + const char *ops_str[] = { + "Install", + "Configure", + "Remove", + "Purge", + "ConfigurePending", + "TriggersPending", + }; fprintf(report, "AptOrdering:\n"); for (vector::iterator I = List.begin(); I != List.end(); ++I) if ((*I).Pkg != NULL)