diff options
-rw-r--r-- | apt-pkg/deb/dpkgpm.cc | 338 | ||||
-rw-r--r-- | apt-pkg/deb/dpkgpm.h | 14 | ||||
-rw-r--r-- | apt-pkg/install-progress.cc | 305 | ||||
-rw-r--r-- | apt-pkg/install-progress.h | 137 | ||||
-rw-r--r-- | apt-pkg/makefile | 5 | ||||
-rw-r--r-- | apt-pkg/packagemanager.cc | 49 | ||||
-rw-r--r-- | apt-pkg/packagemanager.h | 16 | ||||
-rw-r--r-- | apt-private/private-install.cc | 33 | ||||
-rwxr-xr-x | test/integration/test-apt-progress-fd | 51 | ||||
-rwxr-xr-x | test/integration/test-apt-progress-fd-deb822 | 64 |
10 files changed, 735 insertions, 277 deletions
diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index ae895c9e6..98fb7581a 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -19,6 +19,7 @@ #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> @@ -54,15 +55,12 @@ 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) + 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 @@ -71,10 +69,7 @@ public: 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 @@ -137,6 +132,20 @@ ionice(int PID) 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() { @@ -513,7 +522,7 @@ 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); if (Debug == true) @@ -581,28 +590,16 @@ void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line) { if(action == "error") { - 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; + d->progress->Error(list[1], PackagesDone, PackagesTotal, + list[3]); pkgFailures++; WriteApportReport(list[1].c_str(), list[3].c_str()); return; } else if(action == "conffile") { - 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; + d->progress->ConffilePrompt(list[1], PackagesDone, PackagesTotal, + list[3]); return; } } @@ -655,16 +652,8 @@ void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line) return; } std::string msg; - strprintf(msg, _(iter->second), short_pkgname.c_str()); - - status << "pmstatus:" << short_pkgname - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << msg - << endl; - if(OutStatusFd > 0) - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; + 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 @@ -687,23 +676,14 @@ void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line) // action const char *translation = _(states[PackageOpsDone[pkg]].str); std::string msg; - strprintf(msg, translation, short_pkgname.c_str()); - + // we moved from one dpkg state to a new one, report that PackageOpsDone[pkg]++; PackagesDone++; - // build the status str - status << "pmstatus:" << short_pkgname - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << msg - << endl; - if(_config->FindB("DPkgPM::Progress", false) == true) - SendTerminalProgress(PackagesDone/float(PackagesTotal)*100.0); + + strprintf(msg, translation, i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); - if(OutStatusFd > 0) - FileFd::Write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; } if (Debug == true) std::clog << "(parsed from dpkg) pkg: " << short_pkgname @@ -763,7 +743,7 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname) // --------------------------------------------------------------------- /* */ -void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) +void pkgDPkgPM::DoDpkgStatusFd(int statusfd) { char *p, *q; int len; @@ -778,7 +758,7 @@ void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) 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 } @@ -944,50 +924,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 @@ -1009,73 +945,81 @@ 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) +// 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} + }, + }; -void pkgDPkgPM::CleanupTerminal() -{ - // reset scroll area - SetupTerminalScrollArea(d->nr_terminal_rows + 1); - if(d->fancy_progress_output) + // 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) { - // override the progress line (sledgehammer) - static const char* clear_screen_below_cursor = "\033[J"; - std::cout << clear_screen_below_cursor; - std::flush(std::cout); + 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++; + } } } - - + /*}}}*/ // 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; 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"); @@ -1132,54 +1076,8 @@ bool pkgDPkgPM::Go(int OutStatusFd) 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; @@ -1201,8 +1099,9 @@ bool pkgDPkgPM::Go(int OutStatusFd) dpkgMultiArch = true; } - // this loop is runs once per operation - for (vector<Item>::const_iterator I = List.begin(); I != List.end();) + // go over each item + 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; @@ -1403,8 +1302,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) _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?")); @@ -1434,20 +1332,14 @@ bool pkgDPkgPM::Go(int OutStatusFd) _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) { @@ -1484,10 +1376,6 @@ bool pkgDPkgPM::Go(int OutStatusFd) if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0) _exit(100); } - // setup terminal - SetupTerminalScrollArea(d->nr_terminal_rows); - SendTerminalProgress(PackagesDone/float(PackagesTotal)*100.0); - /* No Job Control Stop Env is a magic dpkg var that prevents it from using sigstop */ putenv((char *)"DPKG_NO_TSTP=yes"); @@ -1549,13 +1437,16 @@ bool pkgDPkgPM::Go(int OutStatusFd) 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) @@ -1571,7 +1462,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) if(master >= 0 && FD_ISSET(0, &rfds)) DoStdin(master); if(FD_ISSET(_dpkgin, &rfds)) - DoDpkgStatusFd(_dpkgin, OutStatusFd); + DoDpkgStatusFd(_dpkgin); } close(_dpkgin); @@ -1611,7 +1502,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) if(stopOnError) { CloseLog(); - CleanupTerminal(); + d->progress->Stop(); return false; } } @@ -1619,10 +1510,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) CloseLog(); // dpkg is done at this point - if(_config->FindB("DPkgPM::Progress", false) == true) - SendTerminalProgress(100); - - CleanupTerminal(); + d->progress->Stop(); if (pkgPackageManager::SigINTStop) _error->Warning(_("Operation was interrupted before it could finish")); diff --git a/apt-pkg/deb/dpkgpm.h b/apt-pkg/deb/dpkgpm.h index 1a58e1af5..5f00eee5d 100644 --- a/apt-pkg/deb/dpkgpm.h +++ b/apt-pkg/deb/dpkgpm.h @@ -23,6 +23,7 @@ using std::map; class pkgDPkgPMPrivate; + class pkgDPkgPM : public pkgPackageManager { private: @@ -83,11 +84,12 @@ class pkgDPkgPM : public pkgPackageManager __deprecated bool SendV2Pkgs(FILE *F); bool SendPkgsInfo(FILE * const F, unsigned int const &Version); void WriteHistoryTag(std::string const &tag, std::string value); + std::string ExpandShortPackageName(pkgDepCache &Cache, + const std::string &short_pkgname); // Terminal progress void SetupTerminalScrollArea(int nr_scrolled_rows); void SendTerminalProgress(float percentage); - void CleanupTerminal(); // apport integration void WriteApportReport(const char *pkgpath, const char *errormsg); @@ -95,18 +97,22 @@ class pkgDPkgPM : public pkgPackageManager // dpkg log bool OpenLog(); bool CloseLog(); + + // helper + void BuildPackagesProgressMap(); // input processing void DoStdin(int master); void DoTerminalPty(int master); - void DoDpkgStatusFd(int statusfd, int OutStatusFd); - void ProcessDpkgStatusLine(int OutStatusFd, char *line); + void DoDpkgStatusFd(int statusfd); + void ProcessDpkgStatusLine(char *line); // The Actuall installation implementation virtual bool Install(PkgIterator Pkg,std::string File); virtual bool Configure(PkgIterator Pkg); virtual bool Remove(PkgIterator Pkg,bool Purge = false); - virtual bool Go(int StatusFd=-1); + + virtual bool Go(APT::Progress::PackageManager *progress); virtual void Reset(); public: diff --git a/apt-pkg/install-progress.cc b/apt-pkg/install-progress.cc new file mode 100644 index 000000000..9d0d4e3f1 --- /dev/null +++ b/apt-pkg/install-progress.cc @@ -0,0 +1,305 @@ +#include <apt-pkg/configuration.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/install-progress.h> + +#include <apti18n.h> + +#include <termios.h> +#include <sys/ioctl.h> +#include <sstream> +#include <fcntl.h> + +namespace APT { +namespace Progress { + +bool PackageManager::StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) +{ + int reporting_steps = _config->FindI("DpkgPM::Reporting-Steps", 1); + percentage = StepsDone/(float)TotalSteps * 100.0; + strprintf(progress_str, _("Progress: [%3i%%]"), (int)percentage); + + if(percentage < (last_reported_progress + reporting_steps)) + return false; + + return true; +} + +PackageManagerProgressFd::PackageManagerProgressFd(int progress_fd) + : StepsDone(0), StepsTotal(1) +{ + OutStatusFd = progress_fd; +} + +void PackageManagerProgressFd::WriteToStatusFd(std::string s) +{ + if(OutStatusFd <= 0) + return; + FileFd::Write(OutStatusFd, s.c_str(), s.size()); +} + +void PackageManagerProgressFd::Start() +{ + if(OutStatusFd <= 0) + return; + + // FIXME: use SetCloseExec here once it taught about throwing + // exceptions instead of doing _exit(100) on failure + fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC); + + // send status information that we are about to fork dpkg + std::ostringstream status; + status << "pmstatus:dpkg-exec:" + << (StepsDone/float(StepsTotal)*100.0) + << ":" << _("Running dpkg") + << std::endl; + WriteToStatusFd(status.str()); +} + +void PackageManagerProgressFd::Stop() +{ + // clear the Keep-Fd again + _config->Clear("APT::Keep-Fds", OutStatusFd); +} + +void PackageManagerProgressFd::Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) +{ + std::ostringstream status; + status << "pmerror:" << PackageName + << ":" << (StepsDone/float(TotalSteps)*100.0) + << ":" << ErrorMessage + << std::endl; + WriteToStatusFd(status.str()); +} + +void PackageManagerProgressFd::ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) +{ + std::ostringstream status; + status << "pmconffile:" << PackageName + << ":" << (StepsDone/float(TotalSteps)*100.0) + << ":" << ConfMessage + << std::endl; + WriteToStatusFd(status.str()); +} + + +bool PackageManagerProgressFd::StatusChanged(std::string PackageName, + unsigned int xStepsDone, + unsigned int xTotalSteps, + std::string pkg_action) +{ + StepsDone = xStepsDone; + StepsTotal = xTotalSteps; + + // build the status str + std::ostringstream status; + status << "pmstatus:" << StringSplit(PackageName, ":")[0] + << ":" << (StepsDone/float(StepsTotal)*100.0) + << ":" << pkg_action + << std::endl; + WriteToStatusFd(status.str()); + + if(_config->FindB("Debug::APT::Progress::PackageManagerFd", false) == true) + std::cerr << "progress: " << PackageName << " " << xStepsDone + << " " << xTotalSteps << " " << pkg_action + << std::endl; + + + return true; +} + + +PackageManagerProgressDeb822Fd::PackageManagerProgressDeb822Fd(int progress_fd) + : StepsDone(0), StepsTotal(1) +{ + OutStatusFd = progress_fd; +} + +void PackageManagerProgressDeb822Fd::WriteToStatusFd(std::string s) +{ + FileFd::Write(OutStatusFd, s.c_str(), s.size()); +} + +void PackageManagerProgressDeb822Fd::Start() +{ + // FIXME: use SetCloseExec here once it taught about throwing + // exceptions instead of doing _exit(100) on failure + fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC); + + // send status information that we are about to fork dpkg + std::ostringstream status; + status << "Status: " << "progress" << std::endl + << "Percent: " << (StepsDone/float(StepsTotal)*100.0) << std::endl + << "Message: " << _("Running dpkg") << std::endl + << std::endl; + WriteToStatusFd(status.str()); +} + +void PackageManagerProgressDeb822Fd::Stop() +{ + // clear the Keep-Fd again + _config->Clear("APT::Keep-Fds", OutStatusFd); +} + +void PackageManagerProgressDeb822Fd::Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) +{ + std::ostringstream status; + status << "Status: " << "Error" << std::endl + << "Package:" << PackageName << std::endl + << "Percent: " << (StepsDone/float(TotalSteps)*100.0) << std::endl + << "Message: " << ErrorMessage << std::endl + << std::endl; + WriteToStatusFd(status.str()); +} + +void PackageManagerProgressDeb822Fd::ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) +{ + std::ostringstream status; + status << "Status: " << "ConfFile" << std::endl + << "Package:" << PackageName << std::endl + << "Percent: " << (StepsDone/float(TotalSteps)*100.0) << std::endl + << "Message: " << ConfMessage << std::endl + << std::endl; + WriteToStatusFd(status.str()); +} + + +bool PackageManagerProgressDeb822Fd::StatusChanged(std::string PackageName, + unsigned int xStepsDone, + unsigned int xTotalSteps, + std::string message) +{ + StepsDone = xStepsDone; + StepsTotal = xTotalSteps; + + // build the status str + std::ostringstream status; + status << "Status: " << "progress" << std::endl + << "Package: " << PackageName << std::endl + << "Percent: " << (StepsDone/float(StepsTotal)*100.0) << std::endl + << "Message: " << message << std::endl + << std::endl; + WriteToStatusFd(status.str()); + + return true; +} + + +void PackageManagerFancy::SetupTerminalScrollArea(int nr_rows) +{ + // 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); +} + +PackageManagerFancy::PackageManagerFancy() + : nr_terminal_rows(-1) +{ + struct winsize win; + if(ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win) == 0) + { + nr_terminal_rows = win.ws_row; + } +} + +void PackageManagerFancy::Start() +{ + if (nr_terminal_rows > 0) + SetupTerminalScrollArea(nr_terminal_rows); +} + +void PackageManagerFancy::Stop() +{ + if (nr_terminal_rows > 0) + { + SetupTerminalScrollArea(nr_terminal_rows + 1); + + // override the progress line (sledgehammer) + static const char* clear_screen_below_cursor = "\033[J"; + std::cout << clear_screen_below_cursor; + } +} + +bool PackageManagerFancy::StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) +{ + if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps, + HumanReadableAction)) + return false; + + int row = 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; + std::flush(std::cout); + last_reported_progress = percentage; + + return true; +} + +bool PackageManagerText::StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) +{ + if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps, HumanReadableAction)) + return false; + + std::cout << progress_str << "\r\n"; + std::flush(std::cout); + + last_reported_progress = percentage; + + return true; +} + + + +}; // namespace progress +}; // namespace apt diff --git a/apt-pkg/install-progress.h b/apt-pkg/install-progress.h new file mode 100644 index 000000000..9c31eac92 --- /dev/null +++ b/apt-pkg/install-progress.h @@ -0,0 +1,137 @@ +#ifndef PKGLIB_IPROGRESS_H +#define PKGLIB_IPROGRESS_H + +#include <string> +#include <unistd.h> + + +namespace APT { +namespace Progress { + + class PackageManager + { + private: + /** \brief dpointer placeholder */ + void *d; + + protected: + std::string progress_str; + float percentage; + int last_reported_progress; + + public: + PackageManager() + : percentage(0.0), last_reported_progress(-1) {}; + virtual ~PackageManager() {}; + + virtual void Start() {}; + virtual void Stop() {}; + + virtual pid_t fork() {return fork(); }; + + virtual void Pulse() {}; + virtual long GetPulseInterval() { + return 500000; + }; + + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) ; + virtual void Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) {}; + virtual void ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) {}; + }; + + class PackageManagerProgressFd : public PackageManager + { + protected: + int OutStatusFd; + int StepsDone; + int StepsTotal; + void WriteToStatusFd(std::string msg); + + public: + PackageManagerProgressFd(int progress_fd); + + virtual void Start(); + virtual void Stop(); + + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction); + virtual void Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage); + virtual void ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage); + + }; + + class PackageManagerProgressDeb822Fd : public PackageManager + { + protected: + int OutStatusFd; + int StepsDone; + int StepsTotal; + void WriteToStatusFd(std::string msg); + + public: + PackageManagerProgressDeb822Fd(int progress_fd); + + virtual void Start(); + virtual void Stop(); + + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction); + virtual void Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage); + virtual void ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage); + }; + + class PackageManagerFancy : public PackageManager + { + protected: + int nr_terminal_rows; + void SetupTerminalScrollArea(int nr_rows); + + public: + PackageManagerFancy(); + virtual void Start(); + virtual void Stop(); + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction); + }; + + class PackageManagerText : public PackageManager + { + public: + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction); + }; + + +}; // namespace Progress +}; // namespace APT + +#endif diff --git a/apt-pkg/makefile b/apt-pkg/makefile index dc943aad4..a90131f80 100644 --- a/apt-pkg/makefile +++ b/apt-pkg/makefile @@ -44,7 +44,7 @@ SOURCE+= pkgcache.cc version.cc depcache.cc \ pkgsystem.cc indexfile.cc pkgcachegen.cc acquire-item.cc \ indexrecords.cc vendor.cc vendorlist.cc cdrom.cc indexcopy.cc \ aptconfiguration.cc cachefilter.cc cacheset.cc edsp.cc \ - upgrade.cc update.cc + install-progress.cc upgrade.cc update.cc HEADERS+= algorithms.h depcache.h pkgcachegen.h cacheiterators.h \ orderlist.h sourcelist.h packagemanager.h tagfile.h \ init.h pkgcache.h version.h progress.h pkgrecords.h \ @@ -52,7 +52,8 @@ HEADERS+= algorithms.h depcache.h pkgcachegen.h cacheiterators.h \ clean.h srcrecords.h cachefile.h versionmatch.h policy.h \ pkgsystem.h indexfile.h metaindex.h indexrecords.h vendor.h \ vendorlist.h cdrom.h indexcopy.h aptconfiguration.h \ - cachefilter.h cacheset.h edsp.h upgrade.h update.h + cachefilter.h cacheset.h edsp.h install-progress.h \ + upgrade.h update.h # Source code for the debian specific components # In theory the deb headers do not need to be exported.. diff --git a/apt-pkg/packagemanager.cc b/apt-pkg/packagemanager.cc index 8c0d2e855..a2079a2e4 100644 --- a/apt-pkg/packagemanager.cc +++ b/apt-pkg/packagemanager.cc @@ -26,7 +26,6 @@ #include <apt-pkg/sptr.h> #include <iostream> -#include <fcntl.h> #include <apti18n.h> /*}}}*/ @@ -1028,32 +1027,54 @@ pkgPackageManager::OrderResult pkgPackageManager::OrderInstall() return Completed; } +// PM::DoInstallPostFork - compat /*{{{*/ +// --------------------------------------------------------------------- + /*}}}*/ +pkgPackageManager::OrderResult +pkgPackageManager::DoInstallPostFork(int statusFd) +{ + APT::Progress::PackageManager *progress = new + APT::Progress::PackageManagerProgressFd(statusFd); + pkgPackageManager::OrderResult res = DoInstallPostFork(progress); + delete progress; + return res; + } /*}}}*/ // PM::DoInstallPostFork - Does install part that happens after the fork /*{{{*/ // --------------------------------------------------------------------- pkgPackageManager::OrderResult -pkgPackageManager::DoInstallPostFork(int statusFd) +pkgPackageManager::DoInstallPostFork(APT::Progress::PackageManager *progress) { - if(statusFd > 0) - // FIXME: use SetCloseExec here once it taught about throwing - // exceptions instead of doing _exit(100) on failure - fcntl(statusFd,F_SETFD,FD_CLOEXEC); - bool goResult = Go(statusFd); - if(goResult == false) - return Failed; - - return Res; + bool goResult = Go(progress); + if(goResult == false) + return Failed; + + return Res; }; - + /*}}}*/ +// PM::DoInstall - Does the installation /*{{{*/ +// --------------------------------------------------------------------- +/* compat */ +pkgPackageManager::OrderResult +pkgPackageManager::DoInstall(int statusFd) +{ + APT::Progress::PackageManager *progress = new + APT::Progress::PackageManagerProgressFd(statusFd); + OrderResult res = DoInstall(progress); + delete progress; + return res; + } + /*}}}*/ // PM::DoInstall - Does the installation /*{{{*/ // --------------------------------------------------------------------- /* This uses the filenames in FileNames and the information in the DepCache to perform the installation of packages.*/ -pkgPackageManager::OrderResult pkgPackageManager::DoInstall(int statusFd) +pkgPackageManager::OrderResult +pkgPackageManager::DoInstall(APT::Progress::PackageManager *progress) { if(DoInstallPreFork() == Failed) return Failed; - return DoInstallPostFork(statusFd); + return DoInstallPostFork(progress); } /*}}}*/ diff --git a/apt-pkg/packagemanager.h b/apt-pkg/packagemanager.h index 1a6a9f01c..8ffac2f13 100644 --- a/apt-pkg/packagemanager.h +++ b/apt-pkg/packagemanager.h @@ -23,7 +23,9 @@ #ifndef PKGLIB_PACKAGEMANAGER_H #define PKGLIB_PACKAGEMANAGER_H +#include <apt-pkg/macros.h> #include <apt-pkg/pkgcache.h> +#include <apt-pkg/install-progress.h> #include <string> #include <iostream> @@ -39,6 +41,8 @@ class pkgDepCache; class pkgSourceList; class pkgOrderList; class pkgRecords; + + class pkgPackageManager : protected pkgCache::Namespace { public: @@ -83,7 +87,7 @@ class pkgPackageManager : protected pkgCache::Namespace virtual bool Install(PkgIterator /*Pkg*/,std::string /*File*/) {return false;}; virtual bool Configure(PkgIterator /*Pkg*/) {return false;}; virtual bool Remove(PkgIterator /*Pkg*/,bool /*Purge*/=false) {return false;}; - virtual bool Go(int statusFd=-1) {return true;}; + virtual bool Go(APT::Progress::PackageManager *progress) {return true;}; virtual void Reset() {}; // the result of the operation @@ -96,7 +100,9 @@ class pkgPackageManager : protected pkgCache::Namespace pkgRecords *Recs); // Do the installation - OrderResult DoInstall(int statusFd=-1); + OrderResult DoInstall(APT::Progress::PackageManager *progress); + // compat + __deprecated OrderResult DoInstall(int statusFd=-1); // stuff that needs to be done before the fork() of a library that // uses apt @@ -106,7 +112,11 @@ class pkgPackageManager : protected pkgCache::Namespace }; // stuff that needs to be done after the fork - OrderResult DoInstallPostFork(int statusFd=-1); + OrderResult DoInstallPostFork(APT::Progress::PackageManager *progress); + // compat + __deprecated OrderResult DoInstallPostFork(int statusFd=-1); + + // ? bool FixMissing(); /** \brief returns all packages dpkg let disappear */ diff --git a/apt-private/private-install.cc b/apt-private/private-install.cc index 643a6b370..d9d46dba0 100644 --- a/apt-private/private-install.cc +++ b/apt-private/private-install.cc @@ -23,6 +23,7 @@ #include <apt-pkg/pkgsystem.h> #include <apt-pkg/pkgrecords.h> #include <apt-pkg/indexfile.h> +#include <apt-pkg/install-progress.h> #include <set> #include <locale.h> @@ -103,8 +104,14 @@ bool InstallPackages(CacheFile &Cache,bool ShwKept,bool Ask, bool Safety) if (_config->FindB("APT::Get::Simulate") == true) { pkgSimulate PM(Cache); + int status_fd = _config->FindI("APT::Status-Fd",-1); - pkgPackageManager::OrderResult Res = PM.DoInstall(status_fd); + APT::Progress::PackageManager *progress = NULL; + if (status_fd > 0) + progress = new APT::Progress::PackageManagerProgressFd(status_fd); + pkgPackageManager::OrderResult Res = PM.DoInstall(progress); + delete progress; + if (Res == pkgPackageManager::Failed) return false; if (Res != pkgPackageManager::Completed) @@ -332,8 +339,28 @@ bool InstallPackages(CacheFile &Cache,bool ShwKept,bool Ask, bool Safety) } _system->UnLock(); - int status_fd = _config->FindI("APT::Status-Fd",-1); - pkgPackageManager::OrderResult Res = PM->DoInstall(status_fd); + + // FIXME: make this a factory + // select the right progress + int status_fd = _config->FindI("APT::Status-Fd", -1); + int status_deb822_fd = _config->FindI("APT::Status-deb822-Fd", -1); + + APT::Progress::PackageManager *progress = NULL; + if (status_deb822_fd > 0) + progress = new APT::Progress::PackageManagerProgressDeb822Fd( + status_deb822_fd); + else if (status_fd > 0) + progress = new APT::Progress::PackageManagerProgressFd(status_fd); + else if(_config->FindB("Dpkg::Progress-Fancy", false) == true) + progress = new APT::Progress::PackageManagerFancy(); + else if (_config->FindB("Dpkg::Progress", + _config->FindB("DpkgPM::Progress", false)) == true) + progress = new APT::Progress::PackageManagerText(); + else + progress = new APT::Progress::PackageManager(); + pkgPackageManager::OrderResult Res = PM->DoInstall(progress); + delete progress; + if (Res == pkgPackageManager::Failed || _error->PendingError() == true) return false; if (Res == pkgPackageManager::Completed) diff --git a/test/integration/test-apt-progress-fd b/test/integration/test-apt-progress-fd index 5f73c8f8c..9d250e949 100755 --- a/test/integration/test-apt-progress-fd +++ b/test/integration/test-apt-progress-fd @@ -18,14 +18,14 @@ testsuccess aptget install testing=0.1 -y -o APT::Status-Fd=3 testequal "dlstatus:1:0:Retrieving file 1 of 1 dlstatus:1:0:Retrieving file 1 of 1 pmstatus:dpkg-exec:0:Running dpkg -pmstatus:testing:0:Installing testing -pmstatus:testing:20:Preparing testing -pmstatus:testing:40:Unpacking testing -pmstatus:testing:60:Preparing to configure testing +pmstatus:testing:0:Installing testing (amd64) +pmstatus:testing:20:Preparing testing (amd64) +pmstatus:testing:40:Unpacking testing (amd64) +pmstatus:testing:60:Preparing to configure testing (amd64) pmstatus:dpkg-exec:60:Running dpkg -pmstatus:testing:60:Configuring testing -pmstatus:testing:80:Configuring testing -pmstatus:testing:100:Installed testing" cat apt-progress.log +pmstatus:testing:60:Configuring testing (amd64) +pmstatus:testing:80:Configuring testing (amd64) +pmstatus:testing:100:Installed testing (amd64)" cat apt-progress.log # upgrade exec 3> apt-progress.log @@ -33,23 +33,22 @@ testsuccess aptget install testing=0.8.15 -y -o APT::Status-Fd=3 testequal "dlstatus:1:0:Retrieving file 1 of 1 dlstatus:1:0:Retrieving file 1 of 1 pmstatus:dpkg-exec:0:Running dpkg -pmstatus:testing:20:Preparing testing -pmstatus:testing:40:Unpacking testing -pmstatus:testing:60:Preparing to configure testing +pmstatus:testing:20:Preparing testing (amd64) +pmstatus:testing:40:Unpacking testing (amd64) +pmstatus:testing:60:Preparing to configure testing (amd64) pmstatus:dpkg-exec:60:Running dpkg -pmstatus:testing:60:Configuring testing -pmstatus:testing:80:Configuring testing -pmstatus:testing:100:Installed testing" cat apt-progress.log +pmstatus:testing:60:Configuring testing (amd64) +pmstatus:testing:80:Configuring testing (amd64) +pmstatus:testing:100:Installed testing (amd64)" cat apt-progress.log # and remove exec 3> apt-progress.log testsuccess aptget remove testing -y -o APT::Status-Fd=3 testequal "pmstatus:dpkg-exec:0:Running dpkg -pmstatus:testing:0:Removing testing -pmstatus:testing:33.3333:Preparing for removal of testing -pmstatus:testing:66.6667:Removing testing -pmstatus:testing:100:Removed testing" cat apt-progress.log - +pmstatus:testing:0:Removing testing (amd64) +pmstatus:testing:33.3333:Preparing for removal of testing (amd64) +pmstatus:testing:66.6667:Removing testing (amd64) +pmstatus:testing:100:Removed testing (amd64)" cat apt-progress.log # install non-native and ensure we get proper progress info exec 3> apt-progress.log @@ -59,13 +58,13 @@ testsuccess aptget install testing2:i386 -y -o APT::Status-Fd=3 testequal "dlstatus:1:0:Retrieving file 1 of 1 dlstatus:1:0:Retrieving file 1 of 1 pmstatus:dpkg-exec:0:Running dpkg -pmstatus:testing2:0:Installing testing2 -pmstatus:testing2:20:Preparing testing2 -pmstatus:testing2:40:Unpacking testing2 -pmstatus:testing2:60:Preparing to configure testing2 +pmstatus:testing2:0:Installing testing2 (i386) +pmstatus:testing2:20:Preparing testing2 (i386) +pmstatus:testing2:40:Unpacking testing2 (i386) +pmstatus:testing2:60:Preparing to configure testing2 (i386) pmstatus:dpkg-exec:60:Running dpkg -pmstatus:testing2:60:Configuring testing2 -pmstatus:testing2:80:Configuring testing2 -pmstatus:testing2:100:Installed testing2" cat apt-progress.log +pmstatus:testing2:60:Configuring testing2 (i386) +pmstatus:testing2:80:Configuring testing2 (i386) +pmstatus:testing2:100:Installed testing2 (i386)" cat apt-progress.log -rm -f apt-progress*.log
\ No newline at end of file +rm -f apt-progress*.log diff --git a/test/integration/test-apt-progress-fd-deb822 b/test/integration/test-apt-progress-fd-deb822 new file mode 100755 index 000000000..9d227942d --- /dev/null +++ b/test/integration/test-apt-progress-fd-deb822 @@ -0,0 +1,64 @@ +#!/bin/sh +set -e + +TESTDIR=$(readlink -f $(dirname $0)) +. $TESTDIR/framework + +setupenvironment +configarchitecture 'amd64' 'i386' + +buildsimplenativepackage 'testing' 'amd64' '0.1' 'stable' +buildsimplenativepackage 'testing' 'all' '0.8.15' 'stable' +buildsimplenativepackage 'testing2' 'amd64,i386' '0.8.15' 'stable' +setupaptarchive + +# install native +exec 3> apt-progress.log +testsuccess aptget install testing=0.1 -y -o APT::Status-deb822-Fd=3 + +testequal "Status: progress +Percent: 0 +Message: Running dpkg + +Status: progress +Package: testing:amd64 +Percent: 0 +Message: Installing testing (amd64) + +Status: progress +Package: testing:amd64 +Percent: 20 +Message: Preparing testing (amd64) + +Status: progress +Package: testing:amd64 +Percent: 40 +Message: Unpacking testing (amd64) + +Status: progress +Package: testing:amd64 +Percent: 60 +Message: Preparing to configure testing (amd64) + +Status: progress +Percent: 60 +Message: Running dpkg + +Status: progress +Package: testing:amd64 +Percent: 60 +Message: Configuring testing (amd64) + +Status: progress +Package: testing:amd64 +Percent: 80 +Message: Configuring testing (amd64) + +Status: progress +Package: testing:amd64 +Percent: 100 +Message: Installed testing (amd64) +" cat apt-progress.log + + +rm -f apt-progress*.log |