]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/deb/dpkgpm.cc
CMake: Add coverage build type
[apt.git] / apt-pkg / deb / dpkgpm.cc
index 1fbeabbdcd9b6f0df98ef2321ae993d768312ff5..9d1739d68032988ab67247d43e8b9607ab02d0e8 100644 (file)
 // -*- mode: cpp; mode: fold -*-
 // Description                                                         /*{{{*/
 // -*- mode: cpp; mode: fold -*-
 // Description                                                         /*{{{*/
-// $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $
 /* ######################################################################
 
    DPKG Package Manager - Provide an interface to dpkg
 /* ######################################################################
 
    DPKG Package Manager - Provide an interface to dpkg
-   
+
    ##################################################################### */
                                                                        /*}}}*/
 // Includes                                                            /*{{{*/
 #include <config.h>
 
    ##################################################################### */
                                                                        /*}}}*/
 // 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/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/debsystem.h>
+#include <apt-pkg/error.h>
 #include <apt-pkg/fileutl.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/packagemanager.h>
+#include <apt-pkg/strutl.h>
+#include <apt-pkg/statechanges.h>
+#include <apt-pkg/cacheiterators.h>
+#include <apt-pkg/macros.h>
+#include <apt-pkg/pkgcache.h>
+#include <apt-pkg/version.h>
 
 
-#include <unistd.h>
-#include <stdlib.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <fcntl.h>
+#include <grp.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/select.h>
 #include <sys/stat.h>
-#include <sys/types.h>
+#include <sys/time.h>
 #include <sys/wait.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 <sys/types.h>
+#include <dirent.h>
 #include <termios.h>
 #include <termios.h>
+#include <time.h>
 #include <unistd.h>
 #include <unistd.h>
-#include <sys/ioctl.h>
-#include <pty.h>
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <unordered_set>
+#include <vector>
+#include <sstream>
+#include <numeric>
 
 #include <apti18n.h>
                                                                        /*}}}*/
 
 
 #include <apti18n.h>
                                                                        /*}}}*/
 
+extern char **environ;
+
 using namespace std;
 
 using namespace std;
 
-class pkgDPkgPMPrivate 
+APT_PURE static string AptHistoryRequestingUser()                      /*{{{*/
+{
+   const char* EnvKeys[]{"SUDO_UID", "PKEXEC_UID", "PACKAGEKIT_CALLER_UID"};
+
+   for (const auto &Key: EnvKeys)
+   {
+      if (getenv(Key) != nullptr)
+      {
+         int uid = atoi(getenv(Key));
+         if (uid > 0) {
+            struct passwd pwd;
+            struct passwd *result;
+            char buf[255];
+            if (getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0 && result != NULL) {
+               std::string res;
+               strprintf(res, "%s (%d)", pwd.pw_name, uid);
+               return res;
+            }
+         }
+      }
+   }
+   return "";
+}
+                                                                       /*}}}*/
+APT_PURE static unsigned int EnvironmentSize()                         /*{{{*/
+{
+  unsigned int size = 0;
+  char **envp = environ;
+
+  while (*envp != NULL)
+    size += strlen (*envp++) + 1;
+
+  return size;
+}
+                                                                       /*}}}*/
+class pkgDPkgPMPrivate                                                 /*{{{*/
 {
 public:
    pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0),
 {
 public:
    pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0),
-                       term_out(NULL), history_out(NULL), 
-                        last_reported_progress(0.0), nr_terminal_rows(0),
-                        fancy_progress_output(false)
+                       term_out(NULL), history_out(NULL),
+                       progress(NULL), tt_is_valid(false), master(-1),
+                       slave(NULL), protect_slave_from_dying(-1),
+                       direct_stdin(false)
    {
       dpkgbuf[0] = '\0';
    {
       dpkgbuf[0] = '\0';
-      if(_config->FindB("Dpkg::Progress-Fancy", false) == true)
-      {
-         fancy_progress_output = true;
-         _config->Set("DpkgPM::Progress", true);
-      }
+   }
+   ~pkgDPkgPMPrivate()
+   {
    }
    bool stdin_is_dev_null;
    // the buffer we use for the dpkg status-fd reading
    char dpkgbuf[1024];
    }
    bool stdin_is_dev_null;
    // the buffer we use for the dpkg status-fd reading
    char dpkgbuf[1024];
-   int dpkgbuf_pos;
+   size_t dpkgbuf_pos;
    FILE *term_out;
    FILE *history_out;
    string dpkg_error;
    FILE *term_out;
    FILE *history_out;
    string dpkg_error;
+   APT::Progress::PackageManager *progress;
 
 
-   float last_reported_progress;
-   int nr_terminal_rows;
-   bool fancy_progress_output;
-};
+   // pty stuff
+   struct termios tt;
+   bool tt_is_valid;
+   int master;
+   char * slave;
+   int protect_slave_from_dying;
+
+   // signals
+   sigset_t sigmask;
+   sigset_t original_sigmask;
 
 
+   bool direct_stdin;
+};
+                                                                       /*}}}*/
 namespace
 {
   // Maps the dpkg "processing" info to human readable names.  Entry 0
 namespace
 {
   // Maps the dpkg "processing" info to human readable names.  Entry 0
@@ -100,7 +160,7 @@ namespace
     const char *target;
 
   public:
     const char *target;
 
   public:
-    MatchProcessingOp(const char *the_target)
+    explicit MatchProcessingOp(const char *the_target)
       : target(the_target)
     {
     }
       : target(the_target)
     {
     }
@@ -112,13 +172,10 @@ namespace
   };
 }
 
   };
 }
 
-/* helper function to ionice the given PID 
-
- there is no C header for ionice yet - just the syscall interface
- so we use the binary from util-linux
-*/
-static bool
-ionice(int PID)
+// ionice - helper function to ionice the given PID                    /*{{{*/
+/* there is no C header for ionice yet - just the syscall interface
+   so we use the binary from util-linux */
+static bool ionice(int PID)
 {
    if (!FileExists("/usr/bin/ionice"))
       return false;
 {
    if (!FileExists("/usr/bin/ionice"))
       return false;
@@ -136,55 +193,41 @@ ionice(int PID)
    }
    return ExecWait(Process, "ionice");
 }
    }
    return ExecWait(Process, "ionice");
 }
-
-// dpkgChrootDirectory - chrooting for dpkg if needed                  /*{{{*/
-static void dpkgChrootDirectory()
-{
-   std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory");
-   if (chrootDir == "/")
-      return;
-   std::cerr << "Chrooting into " << chrootDir << std::endl;
-   if (chroot(chrootDir.c_str()) != 0)
-      _exit(100);
-   if (chdir("/") != 0)
-      _exit(100);
-}
                                                                        /*}}}*/
                                                                        /*}}}*/
-
-
 // FindNowVersion - Helper to find a Version in "now" state    /*{{{*/
 // ---------------------------------------------------------------------
 // FindNowVersion - Helper to find a Version in "now" state    /*{{{*/
 // ---------------------------------------------------------------------
-/* This is helpful when a package is no longer installed but has residual 
+/* This is helpful when a package is no longer installed but has residual
  * config files
  */
  * config files
  */
-static 
+static
 pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
 {
    pkgCache::VerIterator Ver;
    for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
 pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
 {
    pkgCache::VerIterator Ver;
    for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
-   {
-      pkgCache::VerFileIterator Vf = Ver.FileList();
-      pkgCache::PkgFileIterator F = Vf.File();
-      for (F = Vf.File(); F.end() == false; ++F)
-      {
-         if (F && F.Archive())
-         {
-            if (strcmp(F.Archive(), "now")) 
-               return Ver;
-         }
-      }
-   }
+      for (pkgCache::VerFileIterator Vf = Ver.FileList(); Vf.end() == false; ++Vf)
+        for (pkgCache::PkgFileIterator F = Vf.File(); F.end() == false; ++F)
+        {
+           if (F.Archive() != 0 && strcmp(F.Archive(), "now") == 0)
+              return Ver;
+        }
    return Ver;
 }
                                                                        /*}}}*/
    return Ver;
 }
                                                                        /*}}}*/
+static pkgCache::VerIterator FindToBeRemovedVersion(pkgCache::PkgIterator const &Pkg)/*{{{*/
+{
+   auto const PV = Pkg.CurrentVer();
+   if (PV.end() == false)
+      return PV;
+   return FindNowVersion(Pkg);
+}
+                                                                       /*}}}*/
 
 // DPkgPM::pkgDPkgPM - Constructor                                     /*{{{*/
 // ---------------------------------------------------------------------
 /* */
 
 // DPkgPM::pkgDPkgPM - Constructor                                     /*{{{*/
 // ---------------------------------------------------------------------
 /* */
-pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) 
-   : pkgPackageManager(Cache), PackagesDone(0), PackagesTotal(0)
+pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache)
+   : pkgPackageManager(Cache),d(new pkgDPkgPMPrivate()), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
 {
 {
-   d = new pkgDPkgPMPrivate();
 }
                                                                        /*}}}*/
 // DPkgPM::pkgDPkgPM - Destructor                                      /*{{{*/
 }
                                                                        /*}}}*/
 // DPkgPM::pkgDPkgPM - Destructor                                      /*{{{*/
@@ -244,7 +287,7 @@ bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge)
 {
    if (Pkg.end() == true)
       return false;
 {
    if (Pkg.end() == true)
       return false;
-   
+
    if (Purge == true)
       List.push_back(Item(Item::Purge,Pkg));
    else
    if (Purge == true)
       List.push_back(Item(Item::Purge,Pkg));
    else
@@ -285,14 +328,14 @@ bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
         Top = Top->Child;
         continue;
       }
         Top = Top->Child;
         continue;
       }
-      
+
       while (Top != 0 && Top->Next == 0)
         Top = Top->Parent;
       if (Top != 0)
         Top = Top->Next;
       while (Top != 0 && Top->Next == 0)
         Top = Top->Parent;
       if (Top != 0)
         Top = Top->Next;
-   }   
+   }
    fprintf(F,"\n");
    fprintf(F,"\n");
+
    // Write out the package actions in order.
    for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
    {
    // Write out the package actions in order.
    for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
    {
@@ -300,7 +343,7 @@ bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
         continue;
 
       pkgDepCache::StateCache &S = Cache[I->Pkg];
         continue;
 
       pkgDepCache::StateCache &S = Cache[I->Pkg];
-      
+
       fprintf(F,"%s ",I->Pkg.Name());
 
       // Current version which we are going to replace
       fprintf(F,"%s ",I->Pkg.Name());
 
       // Current version which we are going to replace
@@ -355,13 +398,13 @@ bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
            fprintf(F,"**ERROR**\n");
         else
            fprintf(F,"%s\n",I->File.c_str());
            fprintf(F,"**ERROR**\n");
         else
            fprintf(F,"%s\n",I->File.c_str());
-      }      
+      }
       else if (I->Op == Item::Configure)
         fprintf(F,"**CONFIGURE**\n");
       else if (I->Op == Item::Remove ||
          I->Op == Item::Purge)
         fprintf(F,"**REMOVE**\n");
       else if (I->Op == Item::Configure)
         fprintf(F,"**CONFIGURE**\n");
       else if (I->Op == Item::Remove ||
          I->Op == Item::Purge)
         fprintf(F,"**REMOVE**\n");
-      
+
       if (ferror(F) != 0)
         return false;
    }
       if (ferror(F) != 0)
         return false;
    }
@@ -375,39 +418,55 @@ bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
    that are due to be installed. */
 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
 {
    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;
    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);
+   sighandler_t old_sigint = signal(SIGINT, SIG_IGN);
+   sighandler_t old_sigquit = signal(SIGQUIT, SIG_IGN);
+
    unsigned int Count = 1;
    for (; Opts != 0; Opts = Opts->Next, Count++)
    {
       if (Opts->Value.empty() == true)
          continue;
 
    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;
       if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
         Pos = OptSec.length();
       OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
       // Determine the protocol version
       string OptSec = Opts->Value;
       string::size_type Pos;
       if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
         Pos = OptSec.length();
       OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
-      
+
       unsigned int Version = _config->FindI(OptSec+"::Version",1);
       unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
       unsigned int Version = _config->FindI(OptSec+"::Version",1);
       unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
-      
+
       // Create the pipes
       // Create the pipes
+      std::set<int> KeepFDs;
+      MergeKeepFdsFromConfiguration(KeepFDs);
       int Pipes[2];
       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
       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
       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
       if (Process == 0)
       {
         // Setup the FDs
@@ -420,7 +479,7 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
         strprintf(hookfd, "%d", InfoFD);
         setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1);
 
         strprintf(hookfd, "%d", InfoFD);
         setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1);
 
-        dpkgChrootDirectory();
+        debSystem::DpkgChrootDirectory();
         const char *Args[4];
         Args[0] = "/bin/sh";
         Args[1] = "-c";
         const char *Args[4];
         Args[0] = "/bin/sh";
         Args[1] = "-c";
@@ -429,13 +488,13 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
         execv(Args[0],(char **)Args);
         _exit(100);
       }
         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");
       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","Failed to open new FD");
+         break;
+      }
+
       // Feed it the filenames.
       if (Version <= 1)
       {
       // Feed it the filenames.
       if (Version <= 1)
       {
@@ -448,7 +507,7 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
            // No errors here..
            if (I->File[0] != '/')
               continue;
            // No errors here..
            if (I->File[0] != '/')
               continue;
-           
+
            /* Feed the filename of each package that is pending install
               into the pipe. */
            fprintf(F,"%s\n",I->File.c_str());
            /* Feed the filename of each package that is pending install
               into the pipe. */
            fprintf(F,"%s\n",I->File.c_str());
@@ -460,23 +519,28 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
         SendPkgsInfo(F, Version);
 
       fclose(F);
         SendPkgsInfo(F, Version);
 
       fclose(F);
-      
+
       // Clean up the sub process
       // 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(SIGINT, old_sigint);
+   signal(SIGPIPE, old_sigpipe);
+   signal(SIGQUIT, old_sigquit);
 
 
-   return true;
+   return result;
 }
                                                                        /*}}}*/
 }
                                                                        /*}}}*/
-// DPkgPM::DoStdin - Read stdin and pass to slave pty                  /*{{{*/
+// DPkgPM::DoStdin - Read stdin and pass to master pty                 /*{{{*/
 // ---------------------------------------------------------------------
 /*
 */
 void pkgDPkgPM::DoStdin(int master)
 {
    unsigned char input_buf[256] = {0,}; 
 // ---------------------------------------------------------------------
 /*
 */
 void pkgDPkgPM::DoStdin(int master)
 {
    unsigned char input_buf[256] = {0,}; 
-   ssize_t len = read(0, input_buf, sizeof(input_buf));
+   ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf));
    if (len)
       FileFd::Write(master, input_buf, len);
    else
    if (len)
       FileFd::Write(master, input_buf, len);
    else
@@ -501,48 +565,32 @@ void pkgDPkgPM::DoTerminalPty(int master)
       struct timespec sleepfor = { 0, 500000000 };
       nanosleep(&sleepfor, NULL);
       return;
       struct timespec sleepfor = { 0, 500000000 };
       nanosleep(&sleepfor, NULL);
       return;
-   }  
-   if(len <= 0) 
+   }
+   if(len <= 0)
       return;
    FileFd::Write(1, term_buf, len);
    if(d->term_out)
       fwrite(term_buf, len, sizeof(char), d->term_out);
 }
                                                                        /*}}}*/
       return;
    FileFd::Write(1, term_buf, len);
    if(d->term_out)
       fwrite(term_buf, len, sizeof(char), d->term_out);
 }
                                                                        /*}}}*/
-// DPkgPM::ProcessDpkgStatusBuf                                                /*{{{*/
-// ---------------------------------------------------------------------
-/*
- */
-void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line)
+// DPkgPM::ProcessDpkgStatusBuf                                                /*{{{*/
+void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
 {
    bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false);
 {
    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;
 
    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>'
    /* 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
    // 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
    // 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
@@ -553,119 +601,211 @@ void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line)
         std::clog << "ignoring line: not enough ':'" << std::endl;
       return;
    }
         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)
    {
    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)
+        for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
+           if(Cache[P].Keep() == false || Cache[P].ReInstall() == true)
+           {
+              auto fullname = P.FullName();
+              if (Cache[P].Delete() && PackageOps[fullname].size() <= PackageOpsDone[fullname])
+                 continue;
+              pkgname = std::move(fullname);
+              break;
+           }
    }
    }
-   const char* const pkg = pkgname.c_str();
-   const char* action = list[2].c_str();
+
+   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)", StringSplit(pkgname, ":")[0].c_str(), arch.c_str());
 
    // 'processing' from dpkg looks like
    // 'processing: action: pkg'
 
    // '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,
       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;
       }
       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;
    }
 
       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;
-   }
+      std::vector<struct DpkgState> &states = PackageOps[pkgname];
+      if (action == "triggers-pending")
+      {
+        if (Debug == true)
+           std::clog << "(parsed from dpkg) pkg: " << pkgname
+              << " action: " << action << " (prefix 2 to "
+              << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
 
 
-   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;
+        states.insert(states.begin(), {"installed", N_("Installed %s")});
+        states.insert(states.begin(), {"half-configured", N_("Configuring %s")});
+        PackagesTotal += 2;
+      }
+      else if(PackageOpsDone[pkgname] < states.size())
+      {
+        char const * next_action = states[PackageOpsDone[pkgname]].state;
+        if (next_action)
+        {
+           /*
+           if (action == "half-installed" && strcmp("half-configured", next_action) == 0 &&
+                 PackageOpsDone[pkg] + 2 < states.size() && action == states[PackageOpsDone[pkg] + 2].state)
+           {
+              if (Debug == true)
+                 std::clog << "(parsed from dpkg) pkg: " << short_pkgname << " action: " << action
+                    << " pending trigger defused by unpack" << std::endl;
+              // unpacking a package defuses the pending trigger
+              PackageOpsDone[pkg] += 2;
+              PackagesDone += 2;
+              next_action = states[PackageOpsDone[pkg]].state;
+           }
+           */
+           if (Debug == true)
+              std::clog << "(parsed from dpkg) pkg: " << pkgname
+                 << " action: " << action << " (expected: '" << next_action << "' "
+                 << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
+
+           // check if the package moved to the next dpkg state
+           if(action == next_action)
+           {
+              // only read the translation if there is actually a next action
+              char const * const translation = _(states[PackageOpsDone[pkgname]].str);
+
+              // we moved from one dpkg state to a new one, report that
+              ++PackageOpsDone[pkgname];
+              ++PackagesDone;
+
+              std::string msg;
+              strprintf(msg, translation, i18n_pkgname.c_str());
+              d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
+           }
+           else if (action == "unpacked" && strcmp(next_action, "config-files") == 0)
+           {
+              // in a crossgrade what looked like a remove first is really an unpack over it
+              ++PackageOpsDone[pkgname];
+              ++PackagesDone;
+
+              auto const Pkg = Cache.FindPkg(pkgname);
+              if (likely(Pkg.end() == false))
+              {
+                 auto const Grp = Pkg.Group();
+                 if (likely(Grp.end() == false))
+                 {
+                    for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
+                       if(Cache[P].Install())
+                       {
+                          auto && Ops = PackageOps[P.FullName()];
+                          auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; });
+                          if (unpackOp != Ops.cend())
+                          {
+                             auto const skipped = std::distance(Ops.cbegin(), unpackOp);
+                             PackagesDone += skipped;
+                             PackageOpsDone[P.FullName()] += skipped;
+                             break;
+                          }
+                       }
+                 }
+              }
+           }
+        }
+      }
    }
    }
-   if (Debug == true) 
-      std::clog << "(parsed from dpkg) pkg: " << pkg 
-               << " action: " << action << endl;
 }
                                                                        /*}}}*/
 // DPkgPM::handleDisappearAction                                       /*{{{*/
 void pkgDPkgPM::handleDisappearAction(string const &pkgname)
 {
 }
                                                                        /*}}}*/
 // 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;
    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;
    // the disappeared package was auto-installed - nothing to do
    if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
       return;
@@ -705,40 +845,33 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname)
 }
                                                                        /*}}}*/
 // DPkgPM::DoDpkgStatusFd                                              /*{{{*/
 }
                                                                        /*}}}*/
 // DPkgPM::DoDpkgStatusFd                                              /*{{{*/
-// ---------------------------------------------------------------------
-/*
- */
-void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd)
+void pkgDPkgPM::DoDpkgStatusFd(int statusfd)
 {
 {
-   char *p, *q;
-   int len;
-
-   len=read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos], sizeof(d->dpkgbuf)-d->dpkgbuf_pos);
-   d->dpkgbuf_pos += len;
+   ssize_t const len = read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos],
+        (sizeof(d->dpkgbuf)/sizeof(d->dpkgbuf[0])) - d->dpkgbuf_pos);
    if(len <= 0)
       return;
    if(len <= 0)
       return;
+   d->dpkgbuf_pos += (len / sizeof(d->dpkgbuf[0]));
 
 
-   // process line by line if we have a buffer
-   p = q = d->dpkgbuf;
-   while((q=(char*)memchr(p, '\n', d->dpkgbuf+d->dpkgbuf_pos-p)) != NULL)
+   // process line by line from the buffer
+   char *p = d->dpkgbuf, *q = nullptr;
+   while((q=(char*)memchr(p, '\n', (d->dpkgbuf + d->dpkgbuf_pos) - p)) != nullptr)
    {
    {
-      *q = 0;
-      ProcessDpkgStatusLine(OutStatusFd, p);
-      p=q+1; // continue with next line
+      *q = '\0';
+      ProcessDpkgStatusLine(p);
+      p = q + 1; // continue with next line
    }
 
    }
 
-   // now move the unprocessed bits (after the final \n that is now a 0x0) 
-   // to the start and update d->dpkgbuf_pos
-   p = (char*)memrchr(d->dpkgbuf, 0, d->dpkgbuf_pos);
-   if(p == NULL)
+   // check if we stripped the buffer clean
+   if (p > (d->dpkgbuf + d->dpkgbuf_pos))
+   {
+      d->dpkgbuf_pos = 0;
       return;
       return;
+   }
 
 
-   // we are interessted in the first char *after* 0x0
-   p++;
-
-   // move the unprocessed tail to the start and update pos
-   memmove(d->dpkgbuf, p, p-d->dpkgbuf);
-   d->dpkgbuf_pos = d->dpkgbuf+d->dpkgbuf_pos-p;
+   // otherwise move the unprocessed tail to the start and update pos
+   memmove(d->dpkgbuf, p, (p - d->dpkgbuf));
+   d->dpkgbuf_pos = (d->dpkgbuf + d->dpkgbuf_pos) - p;
 }
                                                                        /*}}}*/
 // DPkgPM::WriteHistoryTag                                             /*{{{*/
 }
                                                                        /*}}}*/
 // DPkgPM::WriteHistoryTag                                             /*{{{*/
@@ -763,7 +896,8 @@ bool pkgDPkgPM::OpenLog()
    // get current time
    char timestr[200];
    time_t const t = time(NULL);
    // get current time
    char timestr[200];
    time_t const t = time(NULL);
-   struct tm const * const tmp = localtime(&t);
+   struct tm tm_buf;
+   struct tm const * const tmp = localtime_r(&t, &tm_buf);
    strftime(timestr, sizeof(timestr), "%F  %T", tmp);
 
    // open terminal log
    strftime(timestr, sizeof(timestr), "%F  %T", tmp);
 
    // open terminal log
@@ -833,6 +967,9 @@ bool pkgDPkgPM::OpenLog()
       }
       if (_config->Exists("Commandline::AsString") == true)
         WriteHistoryTag("Commandline", _config->Find("Commandline::AsString"));
       }
       if (_config->Exists("Commandline::AsString") == true)
         WriteHistoryTag("Commandline", _config->Find("Commandline::AsString"));
+      std::string RequestingUser = AptHistoryRequestingUser();
+      if (RequestingUser != "")
+         WriteHistoryTag("Requested-By", RequestingUser);
       WriteHistoryTag("Install", install);
       WriteHistoryTag("Reinstall", reinstall);
       WriteHistoryTag("Upgrade", upgrade);
       WriteHistoryTag("Install", install);
       WriteHistoryTag("Reinstall", reinstall);
       WriteHistoryTag("Upgrade", upgrade);
@@ -841,7 +978,7 @@ bool pkgDPkgPM::OpenLog()
       WriteHistoryTag("Purge",purge);
       fflush(d->history_out);
    }
       WriteHistoryTag("Purge",purge);
       fflush(d->history_out);
    }
-   
+
    return true;
 }
                                                                        /*}}}*/
    return true;
 }
                                                                        /*}}}*/
@@ -850,7 +987,8 @@ bool pkgDPkgPM::CloseLog()
 {
    char timestr[200];
    time_t t = time(NULL);
 {
    char timestr[200];
    time_t t = time(NULL);
-   struct tm *tmp = localtime(&t);
+   struct tm tm_buf;
+   struct tm *tmp = localtime_r(&t, &tm_buf);
    strftime(timestr, sizeof(timestr), "%F  %T", tmp);
 
    if(d->term_out)
    strftime(timestr, sizeof(timestr), "%F  %T", tmp);
 
    if(d->term_out)
@@ -889,251 +1027,554 @@ bool pkgDPkgPM::CloseLog()
    return true;
 }
                                                                        /*}}}*/
    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
-// that don't have a working implementation.
-// FIXME: Probably can be removed on Lenny+1
-static int racy_pselect(int nfds, fd_set *readfds, fd_set *writefds,
-   fd_set *exceptfds, const struct timespec *timeout,
-   const sigset_t *sigmask)
-{
-   sigset_t origmask;
-   struct timeval tv;
-   int retval;
-
-   tv.tv_sec = timeout->tv_sec;
-   tv.tv_usec = timeout->tv_nsec/1000;
-
-   sigprocmask(SIG_SETMASK, sigmask, &origmask);
-   retval = select(nfds, readfds, writefds, exceptfds, &tv);
-   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);
-}
-
-// DPkgPM::Go - Run the sequence                                       /*{{{*/
-// ---------------------------------------------------------------------
-/* This globs the operations and calls dpkg 
- *   
- * If it is called with "OutStatusFd" set to a valid file descriptor
- * apt will report the install progress over this fd. It maps the
- * dpkg states a package goes through to human readable (and i10n-able)
- * names and calculates a percentage for each step.
-*/
-bool pkgDPkgPM::Go(int OutStatusFd)
-{
-   pkgPackageManager::SigINTStop = false;
-
-   // 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();
-
-   // Stick in any custom dpkg options
-   Configuration::Item const *Opts = _config->Tree("DPkg::Options");
-   if (Opts != 0)
-   {
-      Opts = Opts->Child;
-      for (; Opts != 0; Opts = Opts->Next)
-      {
-        if (Opts->Value.empty() == true)
-           continue;
-        Args.push_back(Opts->Value.c_str());
-        StartSize += Opts->Value.length();
-      }
-   }
-
-   size_t const BaseArgs = Args.size();
-   // we need to detect if we can qualify packages with the architecture or not
-   Args.push_back("--assert-multi-arch");
-   Args.push_back(NULL);
-
-   pid_t dpkgAssertMultiArch = ExecFork();
-   if (dpkgAssertMultiArch == 0)
-   {
-      dpkgChrootDirectory();
-      // redirect everything to the ultimate sink as we only need the exit-status
-      int const nullfd = open("/dev/null", O_RDONLY);
-      dup2(nullfd, STDIN_FILENO);
-      dup2(nullfd, STDOUT_FILENO);
-      dup2(nullfd, STDERR_FILENO);
-      execvp(Args[0], (char**) &Args[0]);
-      _error->WarningE("dpkgGo", "Can't detect if dpkg supports multi-arch!");
-      _exit(2);
-   }
-
-   fd_set rfds;
-   struct timespec tv;
-   sigset_t sigmask;
-   sigset_t original_sigmask;
-
-   unsigned int const MaxArgs = _config->FindI("Dpkg::MaxArgs",8*1024);
-   unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",32*1024);
-   bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false);
-
-   if (RunScripts("DPkg::Pre-Invoke") == false)
-      return false;
-
-   if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
-      return false;
-
-   // support subpressing of triggers processing for special
-   // cases like d-i that runs the triggers handling manually
-   bool const SmartConf = (_config->Find("PackageManager::Configure", "all") != "all");
-   bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
-   if (_config->FindB("DPkg::ConfigurePending", SmartConf) == true)
-      List.push_back(Item(Item::ConfigurePending, PkgIterator()));
-
    // map the dpkg states to the operations that are performed
    // (this is sorted in the same way as Item::Ops)
    // 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] = {
+   static const std::array<std::array<DpkgState, 3>, 4> DpkgStatesOpMap = {{
       // Install operation
       // Install operation
-      { 
-        {"half-installed", N_("Preparing %s")}, 
-        {"unpacked", N_("Unpacking %s") }, 
-        {NULL, NULL}
-      },
+      {{
+        {"half-installed", N_("Preparing %s")},
+        {"unpacked", N_("Unpacking %s") },
+        {nullptr, nullptr}
+      }},
       // Configure operation
       // Configure operation
-      { 
+      {{
         {"unpacked",N_("Preparing to configure %s") },
         {"half-configured", N_("Configuring %s") },
         { "installed", N_("Installed %s")},
         {"unpacked",N_("Preparing to configure %s") },
         {"half-configured", N_("Configuring %s") },
         { "installed", N_("Installed %s")},
-        {NULL, NULL}
-      },
+      }},
       // Remove operation
       // Remove operation
-      { 
+      {{
         {"half-configured", N_("Preparing for removal of %s")},
         {"half-installed", N_("Removing %s")},
         {"config-files",  N_("Removed %s")},
         {"half-configured", N_("Preparing for removal of %s")},
         {"half-installed", N_("Removing %s")},
         {"config-files",  N_("Removed %s")},
-        {NULL, NULL}
-      },
+      }},
       // Purge operation
       // Purge operation
-      { 
+      {{
         {"config-files", N_("Preparing to completely remove %s")},
         {"not-installed", N_("Completely removed %s")},
         {"config-files", N_("Preparing to completely remove %s")},
         {"not-installed", N_("Completely removed %s")},
-        {NULL, NULL}
-      },
-   };
+        {nullptr, nullptr}
+      }},
+   }};
+   static_assert(Item::Purge == 3, "Enum item has unexpected index for mapping array");
 
    // 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)
 
    // 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)
+   for (auto &&I : List)
    {
    {
-      if((*I).Pkg.end() == true)
+      if(I.Pkg.end() == true)
         continue;
 
         continue;
 
-      string const name = (*I).Pkg.FullName();
+      string const name = I.Pkg.FullName();
       PackageOpsDone[name] = 0;
       PackageOpsDone[name] = 0;
-      for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i)
+      auto AddToPackageOps = std::back_inserter(PackageOps[name]);
+      if (I.Op == Item::Purge && I.Pkg->CurrentVer != 0)
       {
       {
-        PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]);
-        PackagesTotal++;
+        // purging a package which is installed first passes through remove states
+        auto const DpkgOps = DpkgStatesOpMap[Item::Remove];
+        std::copy(DpkgOps.begin(), DpkgOps.end(), AddToPackageOps);
+        PackagesTotal += DpkgOps.size();
       }
       }
+      auto const DpkgOps = DpkgStatesOpMap[I.Op];
+      std::copy_if(DpkgOps.begin(), DpkgOps.end(), AddToPackageOps, [&](DpkgState const &state) {
+        if (state.state == nullptr)
+           return false;
+        ++PackagesTotal;
+        return true;
+      });
    }
    }
+   /* one extra: We don't want the progress bar to reach 100%, especially not
+      if we call dpkg --configure --pending and process a bunch of triggers
+      while showing 100%. Also, spindown takes a while, so never reaching 100%
+      is way more correct than reaching 100% while still doing stuff even if
+      doing it this way is slightly bending the rules */
+   ++PackagesTotal;
+}
+                                                                        /*}}}*/
+bool pkgDPkgPM::Go(int StatusFd)                                       /*{{{*/
+{
+   APT::Progress::PackageManager *progress = NULL;
+   if (StatusFd == -1)
+      progress = APT::Progress::PackageManagerProgressFactory();
+   else
+      progress = new APT::Progress::PackageManagerProgressFd(StatusFd);
 
 
-   d->stdin_is_dev_null = false;
+   return Go(progress);
+}
+                                                                       /*}}}*/
+void pkgDPkgPM::StartPtyMagic()                                                /*{{{*/
+{
+   if (_config->FindB("Dpkg::Use-Pty", true) == false)
+   {
+      d->master = -1;
+      if (d->slave != NULL)
+        free(d->slave);
+      d->slave = NULL;
+      return;
+   }
 
 
-   // create log
-   OpenLog();
+   if (isatty(STDIN_FILENO) == 0)
+      d->direct_stdin = true;
 
 
-   bool dpkgMultiArch = false;
-   if (dpkgAssertMultiArch > 0)
+   _error->PushToStack();
+
+   d->master = posix_openpt(O_RDWR | O_NOCTTY);
+   if (d->master == -1)
+      _error->Errno("posix_openpt", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
+   else if (unlockpt(d->master) == -1)
+      _error->Errno("unlockpt", "Unlocking the slave of master fd %d failed!", d->master);
+   else
    {
    {
-      int Status = 0;
-      while (waitpid(dpkgAssertMultiArch, &Status, 0) != dpkgAssertMultiArch)
+#ifdef HAVE_PTSNAME_R
+      char slave_name[64];     // 64 is used by bionic
+      if (ptsname_r(d->master, slave_name, sizeof(slave_name)) != 0)
+#else
+      char const * const slave_name = ptsname(d->master);
+      if (slave_name == NULL)
+#endif
+        _error->Errno("ptsname", "Getting name for slave of master fd %d failed!", d->master);
+      else
+      {
+        d->slave = strdup(slave_name);
+        if (d->slave == NULL)
+           _error->Errno("strdup", "Copying name %s for slave of master fd %d failed!", slave_name, d->master);
+        else if (grantpt(d->master) == -1)
+           _error->Errno("grantpt", "Granting access to slave %s based on master fd %d failed!", slave_name, d->master);
+        else if (tcgetattr(STDIN_FILENO, &d->tt) == 0)
+        {
+           d->tt_is_valid = true;
+           struct termios raw_tt;
+           // copy window size of stdout if its a 'good' terminal
+           if (tcgetattr(STDOUT_FILENO, &raw_tt) == 0)
+           {
+              struct winsize win;
+              if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0)
+                 _error->Errno("ioctl", "Getting TIOCGWINSZ from stdout failed!");
+              if (ioctl(d->master, TIOCSWINSZ, &win) < 0)
+                 _error->Errno("ioctl", "Setting TIOCSWINSZ for master fd %d failed!", d->master);
+           }
+           if (tcsetattr(d->master, TCSANOW, &d->tt) == -1)
+              _error->Errno("tcsetattr", "Setting in Start via TCSANOW for master fd %d failed!", d->master);
+
+           raw_tt = d->tt;
+           cfmakeraw(&raw_tt);
+           raw_tt.c_lflag &= ~ECHO;
+           raw_tt.c_lflag |= ISIG;
+           // block SIGTTOU during tcsetattr to prevent a hang if
+           // the process is a member of the background process group
+           // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html
+           sigemptyset(&d->sigmask);
+           sigaddset(&d->sigmask, SIGTTOU);
+           sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask);
+           if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_tt) == -1)
+              _error->Errno("tcsetattr", "Setting in Start via TCSAFLUSH for stdin failed!");
+           sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL);
+
+        }
+        if (d->slave != NULL)
+        {
+           /* on linux, closing (and later reopening) all references to the slave
+              makes the slave a death end, so we open it here to have one open all
+              the time. We could use this fd in SetupSlavePtyMagic() for linux, but
+              on kfreebsd we get an incorrect ("step like") output then while it has
+              no problem with closing all references… so to avoid platform specific
+              code here we combine both and be happy once more */
+           d->protect_slave_from_dying = open(d->slave, O_RDWR | O_CLOEXEC | O_NOCTTY);
+        }
+      }
+   }
+
+   if (_error->PendingError() == true)
+   {
+      if (d->master != -1)
+      {
+        close(d->master);
+        d->master = -1;
+      }
+      if (d->slave != NULL)
+      {
+        free(d->slave);
+        d->slave = NULL;
+      }
+      _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
+   }
+   _error->RevertToStack();
+}
+                                                                       /*}}}*/
+void pkgDPkgPM::SetupSlavePtyMagic()                                   /*{{{*/
+{
+   if(d->master == -1 || d->slave == NULL)
+      return;
+
+   if (close(d->master) == -1)
+      _error->FatalE("close", "Closing master %d in child failed!", d->master);
+   d->master = -1;
+   if (setsid() == -1)
+      _error->FatalE("setsid", "Starting a new session for child failed!");
+
+   int const slaveFd = open(d->slave, O_RDWR | O_NOCTTY);
+   if (slaveFd == -1)
+      _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
+   else if (ioctl(slaveFd, TIOCSCTTY, 0) < 0)
+      _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd);
+   else
+   {
+      unsigned short i = 0;
+      if (d->direct_stdin == true)
+        ++i;
+      for (; i < 3; ++i)
+        if (dup2(slaveFd, i) == -1)
+           _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i);
+
+      if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSANOW, &d->tt) < 0)
+        _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd);
+   }
+
+   if (slaveFd != -1)
+      close(slaveFd);
+}
+                                                                       /*}}}*/
+void pkgDPkgPM::StopPtyMagic()                                         /*{{{*/
+{
+   if (d->slave != NULL)
+      free(d->slave);
+   d->slave = NULL;
+   if (d->protect_slave_from_dying != -1)
+   {
+      close(d->protect_slave_from_dying);
+      d->protect_slave_from_dying = -1;
+   }
+   if(d->master >= 0)
+   {
+      if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSAFLUSH, &d->tt) == -1)
+        _error->FatalE("tcsetattr", "Setting in Stop via TCSAFLUSH for stdin failed!");
+      close(d->master);
+      d->master = -1;
+   }
+}
+                                                                       /*}}}*/
+static void cleanUpTmpDir(char * const tmpdir)                         /*{{{*/
+{
+   if (tmpdir == nullptr)
+      return;
+   DIR * const D = opendir(tmpdir);
+   if (D == nullptr)
+      _error->Errno("opendir", _("Unable to read %s"), tmpdir);
+   else
+   {
+      auto const dfd = dirfd(D);
+      for (struct dirent *Ent = readdir(D); Ent != nullptr; Ent = readdir(D))
       {
       {
-        if (errno == EINTR)
+        if (Ent->d_name[0] == '.')
            continue;
            continue;
-        _error->WarningE("dpkgGo", _("Waited for %s but it wasn't there"), "dpkg --assert-multi-arch");
-        break;
+#ifdef _DIRENT_HAVE_D_TYPE
+        if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN))
+           continue;
+#endif
+        if (unlikely(unlinkat(dfd, Ent->d_name, 0) != 0))
+           break;
+      }
+      closedir(D);
+      rmdir(tmpdir);
+   }
+   free(tmpdir);
+}
+                                                                       /*}}}*/
+
+// DPkgPM::Go - Run the sequence                                       /*{{{*/
+// ---------------------------------------------------------------------
+/* This globs the operations and calls dpkg
+ *
+ * If it is called with a progress object apt will report the install
+ * progress to this object. It maps the dpkg states a package goes
+ * through to human readable (and i10n-able)
+ * names and calculates a percentage for each step.
+ */
+static bool ItemIsEssential(pkgDPkgPM::Item const &I)
+{
+   static auto const cachegen = _config->Find("pkgCacheGen::Essential");
+   if (cachegen == "none" || cachegen == "native")
+      return true;
+   if (unlikely(I.Pkg.end()))
+      return true;
+   return (I.Pkg->Flags & pkgCache::Flag::Essential) != 0;
+}
+bool pkgDPkgPM::ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache)
+{
+   {
+      std::unordered_set<decltype(pkgCache::Package::ID)> alreadyRemoved;
+      for (auto && I : List)
+        if (I.Op == Item::Remove || I.Op == Item::Purge)
+           alreadyRemoved.insert(I.Pkg->ID);
+      std::remove_reference<decltype(List)>::type AppendList;
+      for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+        if (Cache[Pkg].Delete() && alreadyRemoved.insert(Pkg->ID).second == true)
+           AppendList.emplace_back(Cache[Pkg].Purge() ? Item::Purge : Item::Remove, Pkg);
+      std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
+   }
+   {
+      std::unordered_set<decltype(pkgCache::Package::ID)> alreadyConfigured;
+      for (auto && I : List)
+        if (I.Op == Item::Configure)
+           alreadyConfigured.insert(I.Pkg->ID);
+      std::remove_reference<decltype(List)>::type AppendList;
+      for (auto && I : List)
+        if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true)
+           AppendList.emplace_back(Item::Configure, I.Pkg);
+      for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+        if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && alreadyConfigured.insert(Pkg->ID).second == true)
+           AppendList.emplace_back(Item::Configure, Pkg);
+      std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
+   }
+   return true;
+}
+bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
+{
+   // explicitely remove&configure everything for hookscripts and progress building
+   // we need them only temporarily through, so keep the length and erase afterwards
+   decltype(List)::const_iterator::difference_type explicitIdx =
+      std::distance(List.cbegin(), List.cend());
+   ExpandPendingCalls(List, Cache);
+
+   /* if dpkg told us that it has already done everything to the package we wanted it to do,
+      we shouldn't ask it for "more" later. That can e.g. happen if packages without conffiles
+      are purged as they will have pass through the purge states on remove already */
+   auto const StripAlreadyDoneFrom = [&](APT::VersionVector & Pending) {
+      Pending.erase(std::remove_if(Pending.begin(), Pending.end(), [&](pkgCache::VerIterator const &Ver) {
+           auto const PN = Ver.ParentPkg().FullName();
+           auto const POD = PackageOpsDone.find(PN);
+           if (POD == PackageOpsDone.end())
+              return false;
+           return PackageOps[PN].size() <= POD->second;
+        }), Pending.end());
+   };
+
+   pkgPackageManager::SigINTStop = false;
+   d->progress = progress;
+
+   // Generate the base argument list for dpkg
+   std::vector<std::string> const sArgs = debSystem::GetDpkgBaseCommand();
+   std::vector<const char *> Args(sArgs.size(), NULL);
+   std::transform(sArgs.begin(), sArgs.end(), Args.begin(),
+        [](std::string const &s) { return s.c_str(); });
+   unsigned long long const StartSize = std::accumulate(sArgs.begin(), sArgs.end(), 0llu,
+        [](unsigned long long const i, std::string const &s) { return i + s.length(); });
+   size_t const BaseArgs = Args.size();
+
+   fd_set rfds;
+   struct timespec tv;
+
+   // try to figure out the max environment size
+   int OSArgMax = sysconf(_SC_ARG_MAX);
+   if(OSArgMax < 0)
+      OSArgMax = 32*1024;
+   OSArgMax -= EnvironmentSize() - 2*1024;
+   unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax);
+   bool const NoTriggers = _config->FindB("DPkg::NoTriggers", true);
+
+   if (RunScripts("DPkg::Pre-Invoke") == false)
+      return false;
+
+   if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
+      return false;
+
+   auto const noopDPkgInvocation = _config->FindB("Debug::pkgDPkgPM",false);
+   // store auto-bits as they are supposed to be after dpkg is run
+   if (noopDPkgInvocation == false)
+      Cache.writeStateFile(NULL);
+
+   bool dpkg_recursive_install = _config->FindB("dpkg::install::recursive", false);
+   if (_config->FindB("dpkg::install::recursive::force", false) == false)
+   {
+      // dpkg uses a sorted treewalk since that version which enables the workaround to work
+      auto const dpkgpkg = Cache.FindPkg("dpkg");
+      if (likely(dpkgpkg.end() == false && dpkgpkg->CurrentVer != 0))
+        dpkg_recursive_install = Cache.VS().CmpVersion("1.18.5", dpkgpkg.CurrentVer().VerStr()) <= 0;
+   }
+   // no point in doing this dance for a handful of packages only
+   unsigned int const dpkg_recursive_install_min = _config->FindB("dpkg::install::recursive::minimum", 5);
+   // FIXME: workaround for dpkg bug, see our ./test-bug-740843-versioned-up-down-breaks test
+   bool const dpkg_recursive_install_numbered = _config->FindB("dpkg::install::recursive::numbered", true);
+
+   // for the progress
+   BuildPackagesProgressMap();
+
+   APT::StateChanges approvedStates;
+   if (_config->FindB("dpkg::selection::remove::approved", true))
+   {
+      for (auto && I : List)
+        if (I.Op == Item::Purge)
+           approvedStates.Purge(FindToBeRemovedVersion(I.Pkg));
+        else if (I.Op == Item::Remove)
+           approvedStates.Remove(FindToBeRemovedVersion(I.Pkg));
+   }
+
+   // Skip removes if we install another architecture of this package soon (crossgrade)
+   // We can't just skip them all the time as it could be an ordering requirement [of another package]
+   if ((approvedStates.Remove().empty() == false || approvedStates.Purge().empty() == false) &&
+        _config->FindB("dpkg::remove::crossgrade::implicit", true) == true)
+   {
+      std::unordered_set<decltype(pkgCache::Package::ID)> crossgraded;
+      std::vector<std::pair<Item*, std::string>> toCrossgrade;
+      auto const PlanedEnd = std::next(List.begin(), explicitIdx);
+      for (auto I = List.begin(); I != PlanedEnd; ++I)
+      {
+        if (I->Op != Item::Remove && I->Op != Item::Purge)
+           continue;
+
+        auto const Grp = I->Pkg.Group();
+        size_t installedInstances = 0;
+        for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
+           if (Pkg->CurrentVer != 0 || Cache[Pkg].Install())
+              ++installedInstances;
+        if (installedInstances == 2)
+        {
+           auto const FirstInstall = std::find_if_not(I, List.end(),
+                 [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; });
+           auto const LastInstall = std::find_if_not(FirstInstall, List.end(),
+                 [](Item const &i) { return i.Op == Item::Install; });
+           auto const crosser = std::find_if(FirstInstall, LastInstall,
+                 [&I](Item const &i) { return i.Pkg->Group == I->Pkg->Group; });
+           if (crosser != LastInstall)
+           {
+              crossgraded.insert(I->Pkg->ID);
+              toCrossgrade.emplace_back(&(*I), crosser->Pkg.FullName());
+           }
+        }
+      }
+      for (auto I = PlanedEnd; I != List.end(); ++I)
+      {
+        if (I->Op != Item::Remove && I->Op != Item::Purge)
+           continue;
+
+        auto const Grp = I->Pkg.Group();
+        for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
+        {
+           if (Pkg == I->Pkg || Cache[Pkg].Install() == false)
+              continue;
+           toCrossgrade.emplace_back(&(*I), Pkg.FullName());
+           break;
+        }
+      }
+      for (auto C : toCrossgrade)
+      {
+        // we never do purges on packages which are crossgraded, even if "requested"
+        if (C.first->Op == Item::Purge)
+        {
+           C.first->Op = Item::Remove; // crossgrades should never be purged
+           auto && Purges = approvedStates.Purge();
+           auto const Ver = std::find_if(
+#if __GNUC__ >= 5 || (__GNUC_MINOR__ >= 9 && __GNUC__ >= 4)
+              Purges.cbegin(), Purges.cend(),
+#else
+              Purges.begin(), Purges.end(),
+#endif
+              [&C](pkgCache::VerIterator const &V) { return V.ParentPkg() == C.first->Pkg; });
+           approvedStates.Remove(*Ver);
+           Purges.erase(Ver);
+           auto && RemOp = PackageOps[C.first->Pkg.FullName()];
+           if (RemOp.size() == 5)
+           {
+              RemOp.erase(std::next(RemOp.begin(), 3), RemOp.end());
+              PackagesTotal -= 2;
+           }
+           else
+           _error->Warning("Unexpected amount of planned ops for package %s: %lu", C.first->Pkg.FullName().c_str(), RemOp.size());
+        }
+      }
+      if (crossgraded.empty() == false)
+      {
+        auto const oldsize = List.size();
+        List.erase(std::remove_if(List.begin(), PlanedEnd,
+              [&crossgraded](Item const &i){
+                 return (i.Op == Item::Remove || i.Op == Item::Purge) &&
+                    crossgraded.find(i.Pkg->ID) != crossgraded.end();
+              }), PlanedEnd);
+        explicitIdx -= (oldsize - List.size());
       }
       }
-      if (WIFEXITED(Status) == true && WEXITSTATUS(Status) == 0)
-        dpkgMultiArch = true;
    }
 
    }
 
-   // this loop is runs once per operation
-   for (vector<Item>::const_iterator I = List.begin(); I != List.end();)
+   APT::StateChanges currentStates;
+   if (_config->FindB("dpkg::selection::current::saveandrestore", true))
+   {
+      for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+        if (Pkg->CurrentVer == 0)
+           continue;
+        else if (Pkg->SelectedState == pkgCache::State::Purge)
+           currentStates.Purge(FindToBeRemovedVersion(Pkg));
+        else if (Pkg->SelectedState == pkgCache::State::DeInstall)
+           currentStates.Remove(FindToBeRemovedVersion(Pkg));
+      if (currentStates.empty() == false)
+      {
+        APT::StateChanges cleanStates;
+        for (auto && P: currentStates.Remove())
+           cleanStates.Install(P);
+        for (auto && P: currentStates.Purge())
+           cleanStates.Install(P);
+        if (cleanStates.Save(false) == false)
+           return _error->Error("Couldn't clean the currently selected dpkg states");
+      }
+   }
+
+   if (_config->FindB("dpkg::selection::remove::approved", true))
+   {
+      if (approvedStates.Save(false) == false)
+      {
+        _error->Error("Couldn't record the approved state changes as dpkg selection states");
+        if (currentStates.Save(false) == false)
+           _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
+        return false;
+      }
+
+      List.erase(std::next(List.begin(), explicitIdx), List.end());
+
+      std::vector<bool> toBeRemoved(Cache.Head().PackageCount, false);
+      for (auto && I: approvedStates.Remove())
+        toBeRemoved[I.ParentPkg()->ID] = true;
+      for (auto && I: approvedStates.Purge())
+        toBeRemoved[I.ParentPkg()->ID] = true;
+
+      for (auto && I: List)
+        if (I.Op == Item::Remove || I.Op == Item::Purge)
+           toBeRemoved[I.Pkg->ID] = false;
+
+      bool const RemovePending = std::find(toBeRemoved.begin(), toBeRemoved.end(), true) != toBeRemoved.end();
+      bool const PurgePending = approvedStates.Purge().empty() == false;
+      if (RemovePending != false || PurgePending != false)
+        List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
+      if (RemovePending)
+        List.emplace_back(Item::RemovePending, pkgCache::PkgIterator());
+      if (PurgePending)
+        List.emplace_back(Item::PurgePending, pkgCache::PkgIterator());
+
+      // support subpressing of triggers processing for special
+      // cases like d-i that runs the triggers handling manually
+      if (_config->FindB("DPkg::ConfigurePending", true))
+        List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
+   }
+   bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
+
+   d->stdin_is_dev_null = false;
+
+   // create log
+   OpenLog();
+
+   bool dpkgMultiArch = debSystem::SupportsMultiArch();
+
+   // start pty magic before the loop
+   StartPtyMagic();
+
+   // Tell the progress that its starting and fork dpkg
+   d->progress->Start(d->master);
+
+   // this loop is runs once per dpkg operation
+   vector<Item>::const_iterator I = List.cbegin();
+   while (I != List.end())
    {
       // Do all actions with the same Op in one run
       vector<Item>::const_iterator J = I;
    {
       // Do all actions with the same Op in one run
       vector<Item>::const_iterator J = I;
@@ -1149,37 +1590,19 @@ bool pkgDPkgPM::Go(int OutStatusFd)
               continue;
            break;
         }
               continue;
            break;
         }
+      else if (J->Op == Item::Remove || J->Op == Item::Purge)
+        J = std::find_if(J, List.cend(), [](Item const &I) { return I.Op != Item::Remove && I.Op != Item::Purge; });
       else
       else
-        for (; J != List.end() && J->Op == I->Op; ++J)
-           /* nothing */;
+        J = std::find_if(J, List.cend(), [&J](Item const &I) { return I.Op != J->Op; });
 
 
-      // keep track of allocated strings for multiarch package names
-      std::vector<char *> Packages;
+      auto const size = (J - I) + 10;
 
       // start with the baseset of arguments
 
       // start with the baseset of arguments
-      unsigned long Size = StartSize;
+      auto Size = StartSize;
       Args.erase(Args.begin() + BaseArgs, Args.end());
       Args.erase(Args.begin() + BaseArgs, Args.end());
-
-      // Now check if we are within the MaxArgs limit
-      //
-      // this code below is problematic, because it may happen that
-      // the argument list is split in a way that A depends on B
-      // and they are in the same "--configure A B" run
-      // - with the split they may now be configured in different
-      //   runs, using Immediate-Configure-All can help prevent this.
-      if (J - I > (signed)MaxArgs)
-      {
-        J = I + MaxArgs;
-        unsigned long const size = MaxArgs + 10;
-        Args.reserve(size);
-        Packages.reserve(size);
-      }
-      else
-      {
-        unsigned long const size = (J - I) + 10;
-        Args.reserve(size);
-        Packages.reserve(size);
-      }
+      Args.reserve(size);
+      // keep track of allocated strings for multiarch package names
+      std::vector<char *> Packages(size, nullptr);
 
       int fd[2];
       if (pipe(fd) != 0)
 
       int fd[2];
       if (pipe(fd) != 0)
@@ -1194,20 +1617,22 @@ bool pkgDPkgPM::Go(int OutStatusFd)
       ADDARG(status_fd_buf);
       unsigned long const Op = I->Op;
 
       ADDARG(status_fd_buf);
       unsigned long const Op = I->Op;
 
+      if (NoTriggers == true && I->Op != Item::TriggersPending &&
+         (I->Op != Item::ConfigurePending || std::next(I) != List.end()))
+      {
+        ADDARGC("--no-triggers");
+      }
+
       switch (I->Op)
       {
         case Item::Remove:
       switch (I->Op)
       {
         case Item::Remove:
-        ADDARGC("--force-depends");
-        ADDARGC("--force-remove-essential");
-        ADDARGC("--remove");
-        break;
-        
         case Item::Purge:
         ADDARGC("--force-depends");
         case Item::Purge:
         ADDARGC("--force-depends");
-        ADDARGC("--force-remove-essential");
-        ADDARGC("--purge");
+        if (std::any_of(I, J, ItemIsEssential))
+           ADDARGC("--force-remove-essential");
+        ADDARGC("--remove");
         break;
         break;
-        
+
         case Item::Configure:
         ADDARGC("--configure");
         break;
         case Item::Configure:
         ADDARGC("--configure");
         break;
@@ -1222,39 +1647,103 @@ bool pkgDPkgPM::Go(int OutStatusFd)
         ADDARGC("--pending");
         break;
 
         ADDARGC("--pending");
         break;
 
+        case Item::RemovePending:
+        ADDARGC("--remove");
+        ADDARGC("--pending");
+        break;
+
+        case Item::PurgePending:
+        ADDARGC("--purge");
+        ADDARGC("--pending");
+        break;
+
         case Item::Install:
         ADDARGC("--unpack");
         ADDARGC("--auto-deconfigure");
         break;
       }
 
         case Item::Install:
         ADDARGC("--unpack");
         ADDARGC("--auto-deconfigure");
         break;
       }
 
-      if (NoTriggers == true && I->Op != Item::TriggersPending &&
-         I->Op != Item::ConfigurePending)
-      {
-        ADDARGC("--no-triggers");
-      }
-#undef ADDARGC
+      char * tmpdir_to_free = nullptr;
 
       // Write in the file or package names
       if (I->Op == Item::Install)
       {
 
       // Write in the file or package names
       if (I->Op == Item::Install)
       {
-        for (;I != J && Size < MaxArgBytes; ++I)
+        auto const installsToDo = J - I;
+        if (dpkg_recursive_install == true && dpkg_recursive_install_min < installsToDo)
         {
         {
-           if (I->File[0] != '/')
-              return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
-           Args.push_back(I->File.c_str());
-           Size += I->File.length();
+           std::string tmpdir;
+           strprintf(tmpdir, "%s/apt-dpkg-install-XXXXXX", GetTempDir().c_str());
+           tmpdir_to_free = strndup(tmpdir.data(), tmpdir.length());
+           if (mkdtemp(tmpdir_to_free) == nullptr)
+              return _error->Errno("DPkg::Go", "mkdtemp of %s failed in preparation of calling dpkg unpack", tmpdir_to_free);
+
+           char p = 1;
+           for (auto c = installsToDo - 1; (c = c/10) != 0; ++p);
+           for (unsigned long n = 0; I != J; ++n, ++I)
+           {
+              if (I->File[0] != '/')
+                 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
+              auto file = flNotDir(I->File);
+              if (flExtension(file) != "deb")
+                 file.append(".deb");
+              std::string linkpath;
+              if (dpkg_recursive_install_numbered)
+                 strprintf(linkpath, "%s/%.*lu-%s", tmpdir_to_free, p, n, file.c_str());
+              else
+                 strprintf(linkpath, "%s/%s", tmpdir_to_free, file.c_str());
+              if (symlink(I->File.c_str(), linkpath.c_str()) != 0)
+                 return _error->Errno("DPkg::Go", "Symlinking %s to %s failed!", I->File.c_str(), linkpath.c_str());
+           }
+           ADDARGC("--recursive");
+           ADDARG(tmpdir_to_free);
+        }
+        else
+        {
+           for (;I != J && Size < MaxArgBytes; ++I)
+           {
+              if (I->File[0] != '/')
+                 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
+              Args.push_back(I->File.c_str());
+              Size += I->File.length();
+           }
+        }
+      }
+      else if (I->Op == Item::RemovePending)
+      {
+        ++I;
+        StripAlreadyDoneFrom(approvedStates.Remove());
+        if (approvedStates.Remove().empty())
+           continue;
+      }
+      else if (I->Op == Item::PurgePending)
+      {
+        ++I;
+        // explicit removes of packages without conffiles passthrough the purge states instantly, too.
+        // Setting these non-installed packages up for purging generates 'unknown pkg' warnings from dpkg
+        StripAlreadyDoneFrom(approvedStates.Purge());
+        if (approvedStates.Purge().empty())
+           continue;
+        std::remove_reference<decltype(approvedStates.Remove())>::type approvedRemoves;
+        std::swap(approvedRemoves, approvedStates.Remove());
+        // we apply it again here as an explicit remove in the ordering will have cleared the purge state
+        if (approvedStates.Save(false) == false)
+        {
+           _error->Error("Couldn't record the approved purges as dpkg selection states");
+           if (currentStates.Save(false) == false)
+              _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
+           return false;
         }
         }
+        std::swap(approvedRemoves, approvedStates.Remove());
       }
       else
       {
         string const nativeArch = _config->Find("APT::Architecture");
       }
       else
       {
         string const nativeArch = _config->Find("APT::Architecture");
-        unsigned long const oldSize = I->Op == Item::Configure ? Size : 0;
+        unsigned long const oldSize = I->Pkg.end() == false ? Size : 0;
         for (;I != J && Size < MaxArgBytes; ++I)
         {
            if((*I).Pkg.end() == true)
               continue;
         for (;I != J && Size < MaxArgBytes; ++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 ||
               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 ||
@@ -1268,12 +1757,15 @@ bool pkgDPkgPM::Go(int OutStatusFd)
            {
               pkgCache::VerIterator PkgVer;
               std::string name = I->Pkg.Name();
            {
               pkgCache::VerIterator PkgVer;
               std::string name = I->Pkg.Name();
-              if (Op == Item::Remove || Op == Item::Purge) 
-               {
+              if (Op == Item::Remove)
                  PkgVer = I->Pkg.CurrentVer();
                  PkgVer = I->Pkg.CurrentVer();
-                  if(PkgVer.end() == true)
-                     PkgVer = FindNowVersion(I->Pkg);
-               }
+              else if (Op == Item::Purge)
+              {
+                 // we purge later with --purge --pending, so if it isn't installed (aka rc-only), skip it here
+                 PkgVer = I->Pkg.CurrentVer();
+                 if (PkgVer.end() == true)
+                    continue;
+              }
               else
                  PkgVer = Cache[I->Pkg].InstVerIter(Cache);
               if (strcmp(I->Pkg.Arch(), "none") == 0)
               else
                  PkgVer = Cache[I->Pkg].InstVerIter(Cache);
               if (strcmp(I->Pkg.Arch(), "none") == 0)
@@ -1291,16 +1783,24 @@ bool pkgDPkgPM::Go(int OutStatusFd)
         if (oldSize == Size)
            continue;
       }
         if (oldSize == Size)
            continue;
       }
+#undef ADDARGC
 #undef ADDARG
 
       J = I;
 #undef ADDARG
 
       J = I;
-      
-      if (_config->FindB("Debug::pkgDPkgPM",false) == true)
+
+      if (noopDPkgInvocation == true)
       {
         for (std::vector<const char *>::const_iterator a = Args.begin();
              a != Args.end(); ++a)
            clog << *a << ' ';
         clog << endl;
       {
         for (std::vector<const char *>::const_iterator a = Args.begin();
              a != Args.end(); ++a)
            clog << *a << ' ';
         clog << endl;
+        for (std::vector<char *>::const_iterator p = Packages.begin();
+              p != Packages.end(); ++p)
+           free(*p);
+        Packages.clear();
+        close(fd[0]);
+        close(fd[1]);
+        cleanUpTmpDir(tmpdir_to_free);
         continue;
       }
       Args.push_back(NULL);
         continue;
       }
       Args.push_back(NULL);
@@ -1309,142 +1809,77 @@ bool pkgDPkgPM::Go(int OutStatusFd)
       clog << flush;
       cerr << flush;
 
       clog << flush;
       cerr << flush;
 
-      /* Mask off sig int/quit. We do this because dpkg also does when 
+      /* Mask off sig int/quit. We do this because dpkg also does when
          it forks scripts. What happens is that when you hit ctrl-c it sends
          it forks scripts. What happens is that when you hit ctrl-c it sends
-        it to all processes in the group. Since dpkg ignores the signal 
+        it to all processes in the group. Since dpkg ignores the signal
         it doesn't die but we do! So we must also ignore it */
       sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
       sighandler_t old_SIGINT = signal(SIGINT,SigINT);
         it doesn't die but we do! So we must also ignore it */
       sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
       sighandler_t old_SIGINT = signal(SIGINT,SigINT);
-      
+
       // Check here for any SIGINT
       // Check here for any SIGINT
-      if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install)) 
+      if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install))
          break;
          break;
-      
-      
+
       // ignore SIGHUP as well (debian #463030)
       sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN);
 
       // 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 (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
 
         close(fd[0]); // close the read end of the pipe
 
-        dpkgChrootDirectory();
+        debSystem::DpkgChrootDirectory();
 
         if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
            _exit(100);
 
         if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
         {
 
         if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
            _exit(100);
 
         if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
         {
-           int Flags,dummy;
+           int Flags;
+            int dummy = 0;
            if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
               _exit(100);
            if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
               _exit(100);
-           
+
            // Discard everything in stdin before forking dpkg
            if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
               _exit(100);
            // Discard everything in stdin before forking dpkg
            if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
               _exit(100);
-           
+
            while (read(STDIN_FILENO,&dummy,1) == 1);
            while (read(STDIN_FILENO,&dummy,1) == 1);
-           
+
            if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 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 */
-        putenv((char *)"DPKG_NO_TSTP=yes");
+        // if color support isn't enabled/disabled explicitly tell
+        // dpkg to use the same state apt is using for its color support
+        if (_config->FindB("APT::Color", false) == true)
+           setenv("DPKG_COLORS", "always", 0);
+        else
+           setenv("DPKG_COLORS", "never", 0);
+
         execvp(Args[0], (char**) &Args[0]);
         cerr << "Could not exec dpkg!" << endl;
         _exit(100);
         execvp(Args[0], (char**) &Args[0]);
         cerr << "Could not exec dpkg!" << endl;
         _exit(100);
-      }      
-
-      // apply ionice
-      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;
+      }
 
       // we read from dpkg here
       int const _dpkgin = fd[0];
       close(fd[1]);                        // close the write end of the pipe
 
 
       // we read from dpkg here
       int const _dpkgin = fd[0];
       close(fd[1]);                        // close the write end of the pipe
 
-      if(slave > 0)
-        close(slave);
+      // apply ionice
+      if (_config->FindB("DPkg::UseIoNice", false) == true)
+        ionice(Child);
 
       // setups fds
 
       // 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();
 
       /* free vectors (and therefore memory) as we don't need the included data anymore */
       for (std::vector<char *>::const_iterator p = Packages.begin();
@@ -1453,123 +1888,115 @@ bool pkgDPkgPM::Go(int OutStatusFd)
       Packages.clear();
 
       // the result of the waitpid call
       Packages.clear();
 
       // the result of the waitpid call
+      int Status = 0;
       int res;
       int res;
-      int select_ret;
+      bool waitpid_failure = false;
       while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
         if(res < 0) {
       while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
         if(res < 0) {
-           // FIXME: move this to a function or something, looks ugly here
            // error handling, waitpid returned -1
            if (errno == EINTR)
               continue;
            // error handling, waitpid returned -1
            if (errno == EINTR)
               continue;
-           RunScripts("DPkg::Post-Invoke");
-
-           // Restore sig int/quit
-           signal(SIGQUIT,old_SIGQUIT);
-           signal(SIGINT,old_SIGINT);
-
-           signal(SIGHUP,old_SIGHUP);
-           return _error->Errno("waitpid","Couldn't wait for subprocess");
+           waitpid_failure = true;
+           break;
         }
 
         // wait for input or output here
         FD_ZERO(&rfds);
         }
 
         // wait for input or output here
         FD_ZERO(&rfds);
-        if (master >= 0 && !d->stdin_is_dev_null)
-           FD_SET(0, &rfds); 
+        if (d->master >= 0 && d->direct_stdin == false && d->stdin_is_dev_null == false)
+           FD_SET(STDIN_FILENO, &rfds);
         FD_SET(_dpkgin, &rfds);
         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 (select_ret < 0 && (errno == EINVAL || errno == ENOSYS))
-           select_ret = racy_pselect(max(master, _dpkgin)+1, &rfds, NULL,
-                                     NULL, &tv, &original_sigmask);
-        if (select_ret == 0) 
-           continue;
-        else if (select_ret < 0 && errno == EINTR)
-           continue;
-        else if (select_ret < 0) 
-        {
-           perror("select() returned error");
-           continue;
-        } 
-        
-        if(master >= 0 && FD_ISSET(master, &rfds))
-           DoTerminalPty(master);
-        if(master >= 0 && FD_ISSET(0, &rfds))
-           DoStdin(master);
+        if(d->master >= 0)
+           FD_SET(d->master, &rfds);
+         tv.tv_sec = 0;
+         tv.tv_nsec = d->progress->GetPulseInterval();
+        auto const select_ret = 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;
+        else if (select_ret < 0)
+        {
+           perror("select() returned error");
+           continue;
+        }
+
+        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))
         if(FD_ISSET(_dpkgin, &rfds))
-           DoDpkgStatusFd(_dpkgin, OutStatusFd);
+           DoDpkgStatusFd(_dpkgin);
       }
       close(_dpkgin);
 
       // Restore sig int/quit
       signal(SIGQUIT,old_SIGQUIT);
       signal(SIGINT,old_SIGINT);
       }
       close(_dpkgin);
 
       // Restore sig int/quit
       signal(SIGQUIT,old_SIGQUIT);
       signal(SIGINT,old_SIGINT);
-      
       signal(SIGHUP,old_SIGHUP);
 
       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;
-      }
+      cleanUpTmpDir(tmpdir_to_free);
 
 
-      if(master >= 0) 
+      if (waitpid_failure == true)
       {
       {
-        tcsetattr(0, TCSAFLUSH, &tt);
-        close(master);
+        strprintf(d->dpkg_error, "Sub-process %s couldn't be waited for.",Args[0]);
+        _error->Error("%s", d->dpkg_error.c_str());
+        break;
       }
 
       // Check for an error code.
       if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
       {
       }
 
       // Check for an error code.
       if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
       {
-        // if it was set to "keep-dpkg-runing" then we won't return
+        // if it was set to "keep-dpkg-running" then we won't return
         // here but keep the loop going and just report it as a error
         // for later
         bool const stopOnError = _config->FindB("Dpkg::StopOnError",true);
         // 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));
            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]);
            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;
+      }
    }
    }
+   // dpkg is done at this point
+   StopPtyMagic();
    CloseLog();
 
    CloseLog();
 
-   // dpkg is done at this point
-   if(_config->FindB("DPkgPM::Progress", false) == true)
-      SendTerminalProgress(100);
+   if (d->dpkg_error.empty() == false)
+   {
+      // no point in reseting packages we already completed removal for
+      StripAlreadyDoneFrom(approvedStates.Remove());
+      StripAlreadyDoneFrom(approvedStates.Purge());
+      APT::StateChanges undo;
+      auto && undoRem = approvedStates.Remove();
+      std::move(undoRem.begin(), undoRem.end(), std::back_inserter(undo.Install()));
+      auto && undoPur = approvedStates.Purge();
+      std::move(undoPur.begin(), undoPur.end(), std::back_inserter(undo.Install()));
+      approvedStates.clear();
+      if (undo.Save(false) == false)
+        _error->Error("Couldn't revert dpkg selection for approved remove/purge after an error was encountered!");
+   }
+
+   StripAlreadyDoneFrom(currentStates.Remove());
+   StripAlreadyDoneFrom(currentStates.Purge());
+   if (currentStates.Save(false) == false)
+      _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
 
    if (pkgPackageManager::SigINTStop)
        _error->Warning(_("Operation was interrupted before it could finish"));
 
 
    if (pkgPackageManager::SigINTStop)
        _error->Warning(_("Operation was interrupted before it could finish"));
 
-   if (RunScripts("DPkg::Post-Invoke") == false)
-      return false;
-
-   if (_config->FindB("Debug::pkgDPkgPM",false) == false)
+   if (noopDPkgInvocation == false)
    {
       std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache");
       if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true &&
    {
       std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache");
       if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true &&
-         unlink(oldpkgcache.c_str()) == 0)
+         RemoveFile("pkgDPkgPM::Go", oldpkgcache))
       {
         std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache");
         if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true)
       {
         std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache");
         if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true)
@@ -1582,18 +2009,26 @@ bool pkgDPkgPM::Go(int OutStatusFd)
       }
    }
 
       }
    }
 
-   Cache.writeStateFile(NULL);
-   return true;
+   // disappearing packages can forward their auto-bit
+   if (disappearedPkgs.empty() == false)
+      Cache.writeStateFile(NULL);
+
+   d->progress->Stop();
+
+   if (RunScripts("DPkg::Post-Invoke") == false)
+      return false;
+
+   return d->dpkg_error.empty();
 }
 
 }
 
-void SigINT(int sig) {
+void SigINT(int /*sig*/) {
    pkgPackageManager::SigINTStop = true;
 }
                                                                        /*}}}*/
 // pkgDpkgPM::Reset - Dump the contents of the command list            /*{{{*/
 // ---------------------------------------------------------------------
 /* */
    pkgPackageManager::SigINTStop = true;
 }
                                                                        /*}}}*/
 // pkgDpkgPM::Reset - Dump the contents of the command list            /*{{{*/
 // ---------------------------------------------------------------------
 /* */
-void pkgDPkgPM::Reset() 
+void pkgDPkgPM::Reset()
 {
    List.erase(List.begin(),List.end());
 }
 {
    List.erase(List.begin(),List.end());
 }
@@ -1601,7 +2036,7 @@ void pkgDPkgPM::Reset()
 // pkgDpkgPM::WriteApportReport - write out error report pkg failure   /*{{{*/
 // ---------------------------------------------------------------------
 /* */
 // pkgDpkgPM::WriteApportReport - write out error report pkg failure   /*{{{*/
 // ---------------------------------------------------------------------
 /* */
-void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) 
+void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
 {
    // If apport doesn't exist or isn't installed do nothing
    // This e.g. prevents messages in 'universes' without apport
 {
    // If apport doesn't exist or isn't installed do nothing
    // This e.g. prevents messages in 'universes' without apport
@@ -1609,11 +2044,11 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
    if (apportPkg.end() == true || apportPkg->CurrentVer == 0)
       return;
 
    if (apportPkg.end() == true || apportPkg->CurrentVer == 0)
       return;
 
-   string pkgname, reportfile, srcpkgname, pkgver, arch;
+   string pkgname, reportfile, pkgver, arch;
    string::size_type pos;
    FILE *report;
 
    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;
    {
       std::clog << "configured to not write apport reports" << std::endl;
       return;
@@ -1626,51 +2061,88 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
       return;
    }
 
       return;
    }
 
-   // check if its not a follow up error 
+   // check if its not a follow up error
    const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured");
    if(strstr(errormsg, needle) != NULL) {
       std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl;
       return;
    }
 
    const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured");
    if(strstr(errormsg, needle) != NULL) {
       std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl;
       return;
    }
 
-   // do not report disk-full failures 
+   // do not report disk-full failures
    if(strstr(errormsg, strerror(ENOSPC)) != NULL) {
       std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl;
       return;
    }
 
    if(strstr(errormsg, strerror(ENOSPC)) != NULL) {
       std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl;
       return;
    }
 
-   // do not report out-of-memory failures 
-   if(strstr(errormsg, strerror(ENOMEM)) != NULL) {
+   // do not report out-of-memory failures
+   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;
    }
 
       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;
    }
 
       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('_');
    if(pos != string::npos)
       pkgname = pkgname.substr(0, pos);
 
    // get the pkgname and reportfile
    pkgname = flNotDir(pkgpath);
    pos = pkgname.find('_');
    if(pos != string::npos)
       pkgname = pkgname.substr(0, pos);
 
-   // find the package versin and source package name
+   // find the package version and source package name
    pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
    if (Pkg.end() == true)
    pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
    if (Pkg.end() == true)
-      return;
-   pkgCache::VerIterator Ver = Cache.GetCandidateVer(Pkg);
+   {
+      if (pos == std::string::npos || _config->FindB("dpkg::install::recursive::numbered", true) == false)
+        return;
+      auto const dash = pkgname.find_first_not_of("0123456789");
+      if (dash == std::string::npos || pkgname[dash] != '-')
+        return;
+      pkgname.erase(0, dash + 1);
+      Pkg = Cache.FindPkg(pkgname);
+      if (Pkg.end() == true)
+        return;
+   }
+   pkgCache::VerIterator Ver = Cache.GetCandidateVersion(Pkg);
    if (Ver.end() == true)
       return;
    pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr();
    if (Ver.end() == true)
       return;
    pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr();
-   pkgRecords Recs(Cache);
-   pkgRecords::Parser &Parse = Recs.Lookup(Ver.FileList());
-   srcpkgname = Parse.SourcePkg();
-   if(srcpkgname.empty())
-      srcpkgname = pkgname;
 
    // if the file exists already, we check:
    // - if it was reported already (touched by apport). 
 
    // if the file exists already, we check:
    // - if it was reported already (touched by apport). 
@@ -1678,7 +2150,8 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
    //    we overwrite it. This is the same behaviour as apport
    // - if we have a report with the same pkgversion already
    //   then we skip it
    //    we overwrite it. This is the same behaviour as apport
    // - if we have a report with the same pkgversion already
    //   then we skip it
-   reportfile = flCombine("/var/crash",pkgname+".0.crash");
+   _config->CndSet("Dir::Apport", "var/crash");
+   reportfile = flCombine(_config->FindDir("Dir::Apport", "var/crash"), pkgname+".0.crash");
    if(FileExists(reportfile))
    {
       struct stat buf;
    if(FileExists(reportfile))
    {
       struct stat buf;
@@ -1719,9 +2192,10 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
    fprintf(report, "ProblemType: Package\n");
    fprintf(report, "Architecture: %s\n", arch.c_str());
    time_t now = time(NULL);
    fprintf(report, "ProblemType: Package\n");
    fprintf(report, "Architecture: %s\n", arch.c_str());
    time_t now = time(NULL);
-   fprintf(report, "Date: %s" , ctime(&now));
+   char ctime_buf[26]; // need at least 26 bytes according to ctime(3)
+   fprintf(report, "Date: %s" , ctime_r(&now, ctime_buf));
    fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str());
    fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str());
-   fprintf(report, "SourcePackage: %s\n", srcpkgname.c_str());
+   fprintf(report, "SourcePackage: %s\n", Ver.SourcePkgName());
    fprintf(report, "ErrorMessage:\n %s\n", errormsg);
 
    // ensure that the log is flushed
    fprintf(report, "ErrorMessage:\n %s\n", errormsg);
 
    // ensure that the log is flushed
@@ -1737,6 +2211,22 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
       fprintf(report, "DpkgTerminalLog:\n");
       log = fopen(logfile_name.c_str(),"r");
       if(log != NULL)
       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)
       {
         char buf[1024];
         while( fgets(buf, sizeof(buf), log) != NULL)
@@ -1745,14 +2235,25 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
       }
    }
 
       }
    }
 
-   // log the ordering 
-   const char *ops_str[] = {"Install", "Configure","Remove","Purge"};
+   // log the ordering, see dpkgpm.h and the "Ops" enum there
    fprintf(report, "AptOrdering:\n");
    fprintf(report, "AptOrdering:\n");
-   for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
-      if ((*I).Pkg != NULL)
-         fprintf(report, " %s: %s\n", (*I).Pkg.Name(), ops_str[(*I).Op]);
-      else
-         fprintf(report, " %s: %s\n", "NULL", ops_str[(*I).Op]);
+   for (auto && I : List)
+   {
+      char const * opstr = nullptr;
+      switch (I.Op)
+      {
+        case Item::Install: opstr = "Install"; break;
+        case Item::Configure: opstr = "Configure"; break;
+        case Item::Remove: opstr = "Remove"; break;
+        case Item::Purge: opstr = "Purge"; break;
+        case Item::ConfigurePending: opstr = "ConfigurePending"; break;
+        case Item::TriggersPending: opstr = "TriggersPending"; break;
+        case Item::RemovePending: opstr = "RemovePending"; break;
+        case Item::PurgePending: opstr = "PurgePending"; break;
+      }
+      auto const pkgname = I.Pkg.end() ? "NULL" : I.Pkg.FullName();
+      fprintf(report, " %s: %s\n", pkgname.c_str(), opstr);
+   }
 
    // attach dmesg log (to learn about segfaults)
    if (FileExists("/bin/dmesg"))
 
    // attach dmesg log (to learn about segfaults)
    if (FileExists("/bin/dmesg"))