Fix conffile prompt regression (LP: #1260297)
[ntk/apt.git] / apt-pkg / deb / dpkgpm.cc
index 98fb758..0d73733 100644 (file)
@@ -44,6 +44,7 @@
 #include <unistd.h>
 #include <sys/ioctl.h>
 #include <pty.h>
+#include <stdio.h>
 
 #include <apti18n.h>
                                                                        /*}}}*/
@@ -55,7 +56,7 @@ class pkgDPkgPMPrivate
 public:
    pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0),
                        term_out(NULL), history_out(NULL), 
-                        progress(NULL)
+                        progress(NULL), master(-1), slave(-1)
    {
       dpkgbuf[0] = '\0';
    }
@@ -70,6 +71,16 @@ public:
    FILE *history_out;
    string dpkg_error;
    APT::Progress::PackageManager *progress;
+
+   // pty stuff
+   struct      termios tt;
+   int master;
+   int slave;
+
+   // signals
+   sigset_t sigmask;
+   sigset_t original_sigmask;
+
 };
 
 namespace
@@ -191,7 +202,7 @@ pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
 // ---------------------------------------------------------------------
 /* */
 pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) 
-   : pkgPackageManager(Cache), PackagesDone(0), PackagesTotal(0)
+   : pkgPackageManager(Cache), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
 {
    d = new pkgDPkgPMPrivate();
 }
@@ -406,17 +417,21 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
       unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
       
       // Create the pipes
+      std::set<int> KeepFDs;
+      MergeKeepFdsFromConfiguration(KeepFDs);
       int Pipes[2];
       if (pipe(Pipes) != 0)
         return _error->Errno("pipe","Failed to create IPC pipe to subprocess");
       if (InfoFD != (unsigned)Pipes[0])
         SetCloseExec(Pipes[0],true);
       else
-        _config->Set("APT::Keep-Fds::", Pipes[0]);
+         KeepFDs.insert(Pipes[0]);
+
+
       SetCloseExec(Pipes[1],true);
 
       // Purified Fork for running the script
-      pid_t Process = ExecFork();
+      pid_t Process = ExecFork(KeepFDs);
       if (Process == 0)
       {
         // Setup the FDs
@@ -438,8 +453,6 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
         execv(Args[0],(char **)Args);
         _exit(100);
       }
-      if (InfoFD == (unsigned)Pipes[0])
-        _config->Clear("APT::Keep-Fds", Pipes[0]);
       close(Pipes[0]);
       FILE *F = fdopen(Pipes[1],"w");
       if (F == 0)
@@ -584,7 +597,7 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
       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
+      'status:/etc/compiz.conf/compiz.conf :  conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited
    */
    if (prefix == "status")
    {
@@ -596,7 +609,7 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
          WriteApportReport(list[1].c_str(), list[3].c_str());
          return;
       }
-      else if(action == "conffile")
+      else if(action == "conffile-prompt")
       {
          d->progress->ConffilePrompt(list[1], PackagesDone, PackagesTotal,
                                      list[3]);
@@ -1000,6 +1013,74 @@ void pkgDPkgPM::BuildPackagesProgressMap()
    }
 }
                                                                         /*}}}*/
+#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13)
+bool pkgDPkgPM::Go(int StatusFd)
+{
+   APT::Progress::PackageManager *progress = NULL;
+   if (StatusFd == -1)
+      progress = APT::Progress::PackageManagerProgressFactory();
+   else
+      progress = new APT::Progress::PackageManagerProgressFd(StatusFd);
+   
+   return GoNoABIBreak(progress);
+}
+#endif
+
+void pkgDPkgPM::StartPtyMagic()
+{
+   // setup the pty and stuff
+   struct      winsize win;
+
+   // if tcgetattr does not return zero there was a error
+   // and we do not do any pty magic
+   _error->PushToStack();
+   if (tcgetattr(STDOUT_FILENO, &d->tt) == 0)
+   {
+       ioctl(1, TIOCGWINSZ, (char *)&win);
+       if (_config->FindB("Dpkg::Use-Pty", true) == false)
+       {
+           d->master = d->slave = -1;
+       } else if (openpty(&d->master, &d->slave, NULL, &d->tt, &win) < 0)
+       {
+           _error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
+           d->master = d->slave = -1;
+        } else {
+           struct termios rtt;
+           rtt = d->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(&d->sigmask);
+           sigaddset(&d->sigmask, SIGTTOU);
+           sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask);
+           tcsetattr(0, TCSAFLUSH, &rtt);
+           sigprocmask(SIG_SETMASK, &d->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();
+}
+
+void pkgDPkgPM::StopPtyMagic()
+{
+   if(d->slave > 0)
+      close(d->slave);
+   if(d->master >= 0) 
+   {
+      tcsetattr(0, TCSAFLUSH, &d->tt);
+      close(d->master);
+   }
+}
+
 // DPkgPM::Go - Run the sequence                                       /*{{{*/
 // ---------------------------------------------------------------------
 /* This globs the operations and calls dpkg 
@@ -1009,7 +1090,11 @@ void pkgDPkgPM::BuildPackagesProgressMap()
  * through to human readable (and i10n-able)
  * names and calculates a percentage for each step.
  */
+#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR >= 13)
 bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
+#else
+bool pkgDPkgPM::GoNoABIBreak(APT::Progress::PackageManager *progress)
+#endif
 {
    pkgPackageManager::SigINTStop = false;
    d->progress = progress;
@@ -1056,8 +1141,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 
    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);
@@ -1099,7 +1182,13 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
         dpkgMultiArch = true;
    }
 
-   // go over each item
+   // start pty magic before the loop
+   StartPtyMagic();
+
+   // Tell the progress that its starting and fork dpkg 
+   d->progress->Start();
+
+   // this loop is runs once per dpkg operation
    vector<Item>::const_iterator I = List.begin();
    while (I != List.end())
    {
@@ -1292,67 +1381,24 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       // 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(STDOUT_FILENO, TIOCGWINSZ, (char *)&win);
-        if (openpty(&master, &slave, NULL, &tt, &win) < 0)
-        {
-           _error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?"));
-           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();
-
-      // this is the dpkg status-fd, we need to keep it
-      _config->Set("APT::Keep-Fds::",fd[1]);
-
-      // Tell the progress that its starting and fork dpkg 
-      // FIXME: this is called once per dpkg run which is *too often*
-      d->progress->Start();
-
-      pid_t Child = ExecFork();
-      // This is the child
+      // now run dpkg
+      d->progress->StartDpkg();
+      std::set<int> KeepFDs;
+      KeepFDs.insert(fd[1]);
+      MergeKeepFdsFromConfiguration(KeepFDs);
+      pid_t Child = ExecFork(KeepFDs);
       if (Child == 0)
       {
-
-        if(slave >= 0 && master >= 0) 
+         // This is the child
+        if(d->slave >= 0 && d->master >= 0) 
         {
            setsid();
-           ioctl(slave, TIOCSCTTY, 0);
-           close(master);
-           dup2(slave, 0);
-           dup2(slave, 1);
-           dup2(slave, 2);
-           close(slave);
+           ioctl(d->slave, TIOCSCTTY, 0);
+           close(d->master);
+           dup2(d->slave, 0);
+           dup2(d->slave, 1);
+           dup2(d->slave, 2);
+           close(d->slave);
         }
         close(fd[0]); // close the read end of the pipe
 
@@ -1376,6 +1422,7 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
            if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
               _exit(100);
         }
+
         /* No Job Control Stop Env is a magic dpkg var that prevents it
            from using sigstop */
         putenv((char *)"DPKG_NO_TSTP=yes");
@@ -1388,9 +1435,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       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;
 
@@ -1398,12 +1442,9 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       int const _dpkgin = fd[0];
       close(fd[1]);                        // close the write end of the pipe
 
-      if(slave > 0)
-        close(slave);
-
       // setups fds
-      sigemptyset(&sigmask);
-      sigprocmask(SIG_BLOCK,&sigmask,&original_sigmask);
+      sigemptyset(&d->sigmask);
+      sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask);
 
       /* free vectors (and therefore memory) as we don't need the included data anymore */
       for (std::vector<char *>::const_iterator p = Packages.begin();
@@ -1432,21 +1473,19 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 
         // wait for input or output here
         FD_ZERO(&rfds);
-        if (master >= 0 && !d->stdin_is_dev_null)
+        if (d->master >= 0 && !d->stdin_is_dev_null)
            FD_SET(0, &rfds); 
         FD_SET(_dpkgin, &rfds);
-        if(master >= 0)
-           FD_SET(master, &rfds);
-        tv.tv_sec = 0;
-        tv.tv_nsec = d->progress->GetPulseInterval();
-        select_ret = pselect(max(master, _dpkgin)+1, &rfds, NULL, NULL, 
-                             &tv, &original_sigmask);
+        if(d->master >= 0)
+           FD_SET(d->master, &rfds);
+         tv.tv_sec = 0;
+         tv.tv_nsec = d->progress->GetPulseInterval();
+        select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL, 
+                             &tv, &d->original_sigmask);
         if (select_ret < 0 && (errno == EINVAL || errno == ENOSYS))
-           select_ret = racy_pselect(max(master, _dpkgin)+1, &rfds, NULL,
-                                     NULL, &tv, &original_sigmask);
-
+           select_ret = racy_pselect(max(d->master, _dpkgin)+1, &rfds, NULL,
+                                     NULL, &tv, &d->original_sigmask);
          d->progress->Pulse();
-
         if (select_ret == 0) 
            continue;
         else if (select_ret < 0 && errno == EINTR)
@@ -1457,10 +1496,10 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
            continue;
         } 
         
-        if(master >= 0 && FD_ISSET(master, &rfds))
-           DoTerminalPty(master);
-        if(master >= 0 && FD_ISSET(0, &rfds))
-           DoStdin(master);
+        if(d->master >= 0 && FD_ISSET(d->master, &rfds))
+           DoTerminalPty(d->master);
+        if(d->master >= 0 && FD_ISSET(0, &rfds))
+           DoStdin(d->master);
         if(FD_ISSET(_dpkgin, &rfds))
            DoDpkgStatusFd(_dpkgin);
       }
@@ -1471,13 +1510,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       signal(SIGINT,old_SIGINT);
       
       signal(SIGHUP,old_SIGHUP);
-
-      if(master >= 0) 
-      {
-        tcsetattr(0, TCSAFLUSH, &tt);
-        close(master);
-      }
-
       // Check for an error code.
       if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
       {
@@ -1502,15 +1534,16 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
         if(stopOnError) 
         {
            CloseLog();
+            StopPtyMagic();
             d->progress->Stop();
            return false;
         }
       }      
    }
-   CloseLog();
-
    // dpkg is done at this point
    d->progress->Stop();
+   StopPtyMagic();
+   CloseLog();
 
    if (pkgPackageManager::SigINTStop)
        _error->Warning(_("Operation was interrupted before it could finish"));
@@ -1593,18 +1626,49 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
    }
 
    // do not report out-of-memory failures 
-   if(strstr(errormsg, strerror(ENOMEM)) != NULL) {
+   if(strstr(errormsg, strerror(ENOMEM)) != NULL ||
+      strstr(errormsg, "failed to allocate memory") != NULL) {
       std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl;
       return;
    }
 
-   // do not report dpkg I/O errors
-   // XXX - this message is localized, but this only matches the English version.  This is better than nothing.
-   if(strstr(errormsg, "short read in buffer_copy (")) {
-      std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl;
+   // do not report bugs regarding inaccessible local files
+   if(strstr(errormsg, strerror(ENOENT)) != NULL ||
+      strstr(errormsg, "cannot access archive") != NULL) {
+      std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
+      return;
+   }
+
+   // do not report errors encountered when decompressing packages
+   if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) {
+      std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl;
       return;
    }
 
+   // do not report dpkg I/O errors, this is a format string, so we compare
+   // the prefix and the suffix of the error with the dpkg error message
+   vector<string> io_errors;
+   io_errors.push_back(string("failed to read on buffer copy for %s"));
+   io_errors.push_back(string("failed in write on buffer copy for %s"));
+   io_errors.push_back(string("short read on buffer copy for %s"));
+
+   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('_');
@@ -1692,6 +1756,24 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
       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())
+   {
+      FILE *log = NULL;
+      char buf[1024];
+
+      fprintf(report, "DpkgHistoryLog:\n");
+      log = fopen(histfile_name.c_str(),"r");
+      if(log != NULL)
+      {
         while( fgets(buf, sizeof(buf), log) != NULL)
            fprintf(report, " %s", buf);
         fclose(log);