// Includes /*{{{*/
#include <config.h>
-#include <apt-pkg/dpkgpm.h>
-#include <apt-pkg/error.h>
+#include <apt-pkg/cachefile.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/depcache.h>
-#include <apt-pkg/pkgrecords.h>
-#include <apt-pkg/strutl.h>
+#include <apt-pkg/dpkgpm.h>
+#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
-#include <apt-pkg/cachefile.h>
+#include <apt-pkg/install-progress.h>
#include <apt-pkg/packagemanager.h>
+#include <apt-pkg/pkgrecords.h>
+#include <apt-pkg/strutl.h>
+#include <apt-pkg/cacheiterators.h>
+#include <apt-pkg/macros.h>
+#include <apt-pkg/pkgcache.h>
-#include <unistd.h>
-#include <stdlib.h>
+#include <errno.h>
#include <fcntl.h>
+#include <grp.h>
+#include <pty.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
-#include <sys/types.h>
+#include <sys/time.h>
#include <sys/wait.h>
-#include <signal.h>
-#include <errno.h>
-#include <string.h>
-#include <stdio.h>
-#include <string.h>
-#include <algorithm>
-#include <sstream>
-#include <map>
-#include <pwd.h>
-#include <grp.h>
-#include <iomanip>
-
#include <termios.h>
+#include <time.h>
#include <unistd.h>
-#include <sys/ioctl.h>
-#include <pty.h>
+#include <algorithm>
+#include <cstring>
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
#include <apti18n.h>
/*}}}*/
{
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), master(-1), slave(NULL)
{
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
FILE *term_out;
FILE *history_out;
string dpkg_error;
+ APT::Progress::PackageManager *progress;
+
+ // pty stuff
+ struct termios tt;
+ int master;
+ char * slave;
+
+ // signals
+ sigset_t sigmask;
+ sigset_t original_sigmask;
- float last_reported_progress;
- int nr_terminal_rows;
- bool fancy_progress_output;
};
namespace
return ExecWait(Process, "ionice");
}
+static std::string getDpkgExecutable()
+{
+ 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);
+ }
+ return Tmp;
+}
+
// dpkgChrootDirectory - chrooting for dpkg if needed /*{{{*/
static void dpkgChrootDirectory()
{
// ---------------------------------------------------------------------
/* */
pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache)
- : pkgPackageManager(Cache), PackagesDone(0), PackagesTotal(0)
+ : pkgPackageManager(Cache), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
{
d = new pkgDPkgPMPrivate();
}
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++)
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;
unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
// Create the pipes
+ std::set<int> 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
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)
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::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: <pkg>: <pkg qstate>'
'status: <pkg>:<arch>: <pkg qstate>'
- 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<std::string> list = StringSplit(line, ": ", 3);
- if(list.size() != 3)
+ std::vector<std::string> 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 char *, const char *> * 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)
+ if (prefix == "status")
{
- 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)
- {
- 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<struct DpkgState> 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<struct DpkgState> 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;
// ---------------------------------------------------------------------
/*
*/
-void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd)
+void pkgDPkgPM::DoDpkgStatusFd(int statusfd)
{
char *p, *q;
int len;
while((q=(char*)memchr(p, '\n', d->dpkgbuf+d->dpkgbuf_pos-p)) != NULL)
{
*q = 0;
- ProcessDpkgStatusLine(OutStatusFd, p);
+ ProcessDpkgStatusLine(p);
p=q+1; // continue with next line
}
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
sigprocmask(SIG_SETMASK, &origmask, 0);
return retval;
}
-/*}}}*/
+ /*}}}*/
-void pkgDPkgPM::SetupTerminalScrollArea(int nr_rows)
+// DPkgPM::BuildPackagesProgressMap /*{{{*/
+void pkgDPkgPM::BuildPackagesProgressMap()
{
- 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);
+ // 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] = {
+ // Install operation
+ {
+ {"half-installed", N_("Preparing %s")},
+ {"unpacked", N_("Unpacking %s") },
+ {NULL, NULL}
+ },
+ // Configure operation
+ {
+ {"unpacked",N_("Preparing to configure %s") },
+ {"half-configured", N_("Configuring %s") },
+ { "installed", N_("Installed %s")},
+ {NULL, NULL}
+ },
+ // Remove operation
+ {
+ {"half-configured", N_("Preparing for removal of %s")},
+ {"half-installed", N_("Removing %s")},
+ {"config-files", N_("Removed %s")},
+ {NULL, NULL}
+ },
+ // Purge operation
+ {
+ {"config-files", N_("Preparing to completely remove %s")},
+ {"not-installed", N_("Completely removed %s")},
+ {NULL, NULL}
+ },
+ };
+
+ // init the PackageOps map, go over the list of packages that
+ // that will be [installed|configured|removed|purged] and add
+ // them to the PackageOps map (the dpkg states it goes through)
+ // and the PackageOpsTranslations (human readable strings)
+ for (vector<Item>::const_iterator I = List.begin(); I != List.end(); ++I)
+ {
+ if((*I).Pkg.end() == true)
+ continue;
+
+ string const name = (*I).Pkg.FullName();
+ PackageOpsDone[name] = 0;
+ for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i)
+ {
+ PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]);
+ PackagesTotal++;
+ }
+ }
+}
+ /*}}}*/
+#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13)
+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 GoNoABIBreak(progress);
+}
+#endif
+
+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;
+ }
+
+ _error->PushToStack();
+ // if tcgetattr for both stdin/stdout returns 0 (no error)
+ // we do the pty magic
+ if (tcgetattr(STDOUT_FILENO, &d->tt) == 0 &&
+ tcgetattr(STDIN_FILENO, &d->tt) == 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);
+ close(d->master);
+ d->master = -1;
+ }
+ else
+ {
+ char const * const slave_name = ptsname(d->master);
+ if (slave_name == NULL)
+ {
+ _error->Errno("unlockpt", "Getting name for slave of master fd %d failed!", d->master);
+ close(d->master);
+ d->master = -1;
+ }
+ else
+ {
+ 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);
+ close(d->master);
+ d->master = -1;
+ }
+ 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);
+
+ struct termios raw_tt;
+ 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 stdout failed!");
+ sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL);
+ }
+ }
+ }
+ else
+ {
+ // 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.
+ if (isatty(STDOUT_FILENO) == 1 || errno == EBADF)
+ _error->Errno("tcgetattr", _("Can not write log (%s)"), _("Is stdout a terminal?"));
+ }
+
+ if (_error->PendingError() == true)
+ {
+ if (d->master != -1)
+ {
+ close(d->master);
+ d->master = -1;
+ }
+ _error->DumpErrors(std::cerr);
+ }
+ _error->RevertToStack();
+}
+void pkgDPkgPM::SetupSlavePtyMagic()
+{
+ if(d->master == -1)
+ return;
+
+ if (close(d->master) == -1)
+ _error->FatalE("close", "Closing master %d in child failed!", d->master);
+ if (setsid() == -1)
+ _error->FatalE("setsid", "Starting a new session for child failed!");
+
+ int const slaveFd = open(d->slave, O_RDWR);
+ if (slaveFd == -1)
+ _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
+
+ if (ioctl(slaveFd, TIOCSCTTY, 0) < 0)
+ _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd);
+ else
+ {
+ for (unsigned short i = 0; i < 3; ++i)
+ if (dup2(slaveFd, i) == -1)
+ _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i);
+
+ if (tcsetattr(0, TCSANOW, &d->tt) < 0)
+ _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd);
+ }
+}
+void pkgDPkgPM::StopPtyMagic()
+{
+ if (d->slave != NULL)
+ free(d->slave);
+ d->slave = NULL;
+ if(d->master >= 0)
+ {
+ if (tcsetattr(0, 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 "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)
+ *
+ * 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(int OutStatusFd)
+ */
+#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR >= 13)
+bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
+#else
+bool pkgDPkgPM::GoNoABIBreak(APT::Progress::PackageManager *progress)
+#endif
{
pkgPackageManager::SigINTStop = false;
+ d->progress = progress;
// Generate the base argument list for dpkg
- std::vector<const char *> 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();
+ std::vector<const char *> Args;
+ std::string DpkgExecutable = getDpkgExecutable();
+ Args.push_back(DpkgExecutable.c_str());
+ StartSize += DpkgExecutable.length();
// Stick in any custom dpkg options
Configuration::Item const *Opts = _config->Tree("DPkg::Options");
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);
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] = {
- // Install operation
- {
- {"half-installed", N_("Preparing %s")},
- {"unpacked", N_("Unpacking %s") },
- {NULL, NULL}
- },
- // Configure operation
- {
- {"unpacked",N_("Preparing to configure %s") },
- {"half-configured", N_("Configuring %s") },
- { "installed", N_("Installed %s")},
- {NULL, NULL}
- },
- // Remove operation
- {
- {"half-configured", N_("Preparing for removal of %s")},
- {"half-installed", N_("Removing %s")},
- {"config-files", N_("Removed %s")},
- {NULL, NULL}
- },
- // Purge operation
- {
- {"config-files", N_("Preparing to completely remove %s")},
- {"not-installed", N_("Completely removed %s")},
- {NULL, NULL}
- },
- };
-
- // init the PackageOps map, go over the list of packages that
- // that will be [installed|configured|removed|purged] and add
- // them to the PackageOps map (the dpkg states it goes through)
- // and the PackageOpsTranslations (human readable strings)
- for (vector<Item>::const_iterator I = List.begin(); I != List.end(); ++I)
- {
- if((*I).Pkg.end() == true)
- continue;
-
- string const name = (*I).Pkg.FullName();
- PackageOpsDone[name] = 0;
- for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i)
- {
- PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]);
- PackagesTotal++;
- }
- }
+ // for the progress
+ BuildPackagesProgressMap();
d->stdin_is_dev_null = false;
dpkgMultiArch = true;
}
- // this loop is runs once per operation
- for (vector<Item>::const_iterator I = List.begin(); I != List.end();)
+ // 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<Item>::const_iterator I = List.begin();
+ while (I != List.end())
{
// Do all actions with the same Op in one run
vector<Item>::const_iterator J = I;
{
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 ||
// 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<int> 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();
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);
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 */
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;
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<char *>::const_iterator p = Packages.begin();
// wait for input or output here
FD_ZERO(&rfds);
- if (master >= 0 && !d->stdin_is_dev_null)
+ if (d->master >= 0 && !d->stdin_is_dev_null)
FD_SET(0, &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)
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);
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)
{
// 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"));
}
Cache.writeStateFile(NULL);
- return true;
+ return d->dpkg_error.empty();
}
-void SigINT(int sig) {
+void SigINT(int /*sig*/) {
pkgPackageManager::SigINTStop = true;
}
/*}}}*/
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;
}
// 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<string> 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<string>::iterator I = io_errors.begin(); I != io_errors.end(); ++I)
+ {
+ vector<string> 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('_');
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)