#include <apt-pkg/fileutl.h>
#include <apt-pkg/cachefile.h>
#include <apt-pkg/packagemanager.h>
+#include <apt-pkg/install-progress.h>
#include <unistd.h>
#include <stdlib.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)
+ progress(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;
-
- float last_reported_progress;
- int nr_terminal_rows;
- bool fancy_progress_output;
+ APT::Progress::PackageManager *progress;
};
namespace
// ---------------------------------------------------------------------
/*
*/
-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,configure,remove,purge,disappear,trigproc}: pkg'
+ 'processing: {install,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;
+ ostringstream status;
+
+ // "processing" has the form "processing: action: pkg or trigger"
+ // with action = ["install", "configure", "remove", "purge", "disappear",
+ // "trigproc"]
+ if (prefix == "processing")
+ {
+ pkgname = APT::String::Strip(list[2]);
+ action = APT::String::Strip(list[1]);
+
+ // this is what we support in the processing stage
+ if(action != "install" && action != "configure" &&
+ action != "remove" && action != "purge" && action != "purge")
+ {
+ if (Debug == true)
+ std::clog << "ignoring processing action: '" << action
+ << "'" << std::endl;
+ return;
+ }
+ }
+ // "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: conffile-prompt: conffile : 'current-conffile' 'new-conffile' useredited distedited
+ */
+ if (prefix == "status")
+ {
+ if(action == "error")
+ {
+ d->progress->Error(list[1], PackagesDone, PackagesTotal,
+ list[3]);
+ pkgFailures++;
+ WriteApportReport(list[1].c_str(), list[3].c_str());
+ return;
+ }
+ else if(action == "conffile")
+ {
+ d->progress->ConffilePrompt(list[1], 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 send 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 in a touched by dpkg
+ // if there are multiple dpkg will 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].Mode != pkgDepCache::ModeKeep)
+ {
+ 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)
- {
- 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<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 && (action == next_action))
+ {
+ // only read the translation if there is actually a next
+ // action
+ const char *translation = _(states[PackageOpsDone[pkg]].str);
+ std::string msg;
- 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++;
+
+ strprintf(msg, translation, i18n_pkgname.c_str());
+ d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
+
+ }
+ if (Debug == true)
+ std::clog << "(parsed from dpkg) pkg: " << short_pkgname
+ << " action: " << action << endl;
}
- if (Debug == true)
- std::clog << "(parsed from dpkg) pkg: " << pkg
- << " action: " << action << endl;
}
/*}}}*/
// DPkgPM::handleDisappearAction /*{{{*/
// ---------------------------------------------------------------------
/*
*/
-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)
-{
- 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\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)
+ *
+ * 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)
+ */
+bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
{
pkgPackageManager::SigINTStop = false;
+ d->progress = progress;
// Generate the base argument list for dpkg
std::vector<const char *> Args;
_error->PushToStack();
if (tcgetattr(STDOUT_FILENO, &tt) == 0)
{
- ioctl(1, TIOCGWINSZ, (char *)&win);
- d->nr_terminal_rows = win.ws_row;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win);
if (openpty(&master, &slave, NULL, &tt, &win) < 0)
{
_error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
_error->DumpErrors(std::cerr);
_error->RevertToStack();
- // Fork dpkg
- pid_t Child;
+ // this is the dpkg status-fd, we need to keep it
_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();
+ // Tell the progress that its starting and fork dpkg
+ // FIXME: this is called once per dpkg run which is *too often*
+ d->progress->Start();
+
+ pid_t Child = ExecFork();
// This is the child
if (Child == 0)
{
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");
FD_SET(_dpkgin, &rfds);
if(master >= 0)
FD_SET(master, &rfds);
- tv.tv_sec = 1;
- tv.tv_nsec = 0;
+ tv.tv_sec = 0;
+ tv.tv_nsec = d->progress->GetPulseInterval();
select_ret = pselect(max(master, _dpkgin)+1, &rfds, NULL, NULL,
&tv, &original_sigmask);
if (select_ret < 0 && (errno == EINVAL || errno == ENOSYS))
select_ret = racy_pselect(max(master, _dpkgin)+1, &rfds, NULL,
NULL, &tv, &original_sigmask);
+
+ d->progress->Pulse();
+
if (select_ret == 0)
continue;
else if (select_ret < 0 && errno == EINTR)
if(master >= 0 && FD_ISSET(0, &rfds))
DoStdin(master);
if(FD_ISSET(_dpkgin, &rfds))
- DoDpkgStatusFd(_dpkgin, OutStatusFd);
+ DoDpkgStatusFd(_dpkgin);
}
close(_dpkgin);
signal(SIGHUP,old_SIGHUP);
- // reset scroll area
- SetupTerminalScrollArea(d->nr_terminal_rows);
- 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);
if(stopOnError)
{
CloseLog();
+ d->progress->Stop();
return false;
}
}
CloseLog();
// dpkg is done at this point
- if(_config->FindB("DPkgPM::Progress", false) == true)
- SendTerminalProgress(100);
+ d->progress->Stop();
if (pkgPackageManager::SigINTStop)
_error->Warning(_("Operation was interrupted before it could finish"));