Imported Upstream version 14.45.0
[platform/upstream/libzypp.git] / zypp / ExternalProgram.cc
index 863b4b4..d415298 100644 (file)
@@ -24,6 +24,8 @@
 #include <sstream>
 
 #include "zypp/base/Logger.h"
+#include "zypp/base/String.h"
+#include "zypp/base/Gettext.h"
 #include "zypp/ExternalProgram.h"
 
 using namespace std;
@@ -31,9 +33,10 @@ using namespace std;
 namespace zypp {
 
     ExternalProgram::ExternalProgram()
-    : use_pty (false)
-    {
-    }
+      : use_pty (false)
+      , pid( -1 )
+    {}
+
 
     ExternalProgram::ExternalProgram( std::string commandline,
                                       Stderr_Disposition stderr_disp,
@@ -41,7 +44,8 @@ namespace zypp {
                                       int stderr_fd,
                                       bool default_locale,
                                       const Pathname & root )
-    : use_pty (use_pty)
+      : use_pty (use_pty)
+      , pid( -1 )
     {
       const char *argv[4];
       argv[0] = "/bin/sh";
@@ -49,101 +53,212 @@ namespace zypp {
       argv[2] = commandline.c_str();
       argv[3] = 0;
 
-      const char* rootdir = NULL;
-      if(!root.empty() && root != "/")
-      {
-       rootdir = root.asString().c_str();
-      }
-      Environment environment;
-      start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+      start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
     }
 
 
-    ExternalProgram::ExternalProgram( const char *const *argv,
+    ExternalProgram::ExternalProgram( const Arguments & argv,
                                       Stderr_Disposition stderr_disp,
                                       bool use_pty,
-                                      int stderr_fd,
+                                     int stderr_fd,
                                       bool default_locale,
                                       const Pathname & root )
-    : use_pty (use_pty)
+      : use_pty (use_pty)
+      , pid( -1 )
     {
-      const char* rootdir = NULL;
-      if(!root.empty() && root != "/")
+      const char * argvp[argv.size() + 1];
+      unsigned c = 0;
+      for_( i, argv.begin(), argv.end() )
       {
-       rootdir = root.asString().c_str();
+       argvp[c] = i->c_str();
+       ++c;
       }
-      Environment environment;
-      start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+      argvp[c] = 0;
+
+      start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
     }
 
 
-    ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
-                                 Stderr_Disposition stderr_disp, bool use_pty,
-                                 int stderr_fd, bool default_locale,
-                                 const Pathname& root)
+    ExternalProgram::ExternalProgram( const Arguments & argv,
+                                      const Environment & environment,
+                                      Stderr_Disposition stderr_disp,
+                                      bool use_pty,
+                                     int stderr_fd,
+                                      bool default_locale,
+                                     const Pathname & root )
       : use_pty (use_pty)
+      , pid( -1 )
     {
-      const char* rootdir = NULL;
-      if(!root.empty() && root != "/")
+      const char * argvp[argv.size() + 1];
+      unsigned c = 0;
+      for_( i, argv.begin(), argv.end() )
       {
-       rootdir = root.asString().c_str();
+       argvp[c] = i->c_str();
+       ++c;
       }
-      start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+      argvp[c] = 0;
+
+      start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
     }
 
 
-    ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
-                                 bool use_pty)
+
+    ExternalProgram::ExternalProgram( const char *const *argv,
+                                      Stderr_Disposition stderr_disp,
+                                      bool use_pty,
+                                      int stderr_fd,
+                                      bool default_locale,
+                                      const Pathname & root )
       : use_pty (use_pty)
+      , pid( -1 )
+    {
+      start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
+    }
+
+
+    ExternalProgram::ExternalProgram( const char *const * argv,
+                                     const Environment & environment,
+                                     Stderr_Disposition stderr_disp,
+                                     bool use_pty,
+                                     int stderr_fd,
+                                     bool default_locale,
+                                     const Pathname & root )
+      : use_pty (use_pty)
+      , pid( -1 )
+    {
+      start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
+    }
+
+
+    ExternalProgram::ExternalProgram( const char *binpath,
+                                     const char *const *argv_1,
+                                     bool use_pty )
+      : use_pty (use_pty)
+      , pid( -1 )
     {
       int i = 0;
       while (argv_1[i++])
        ;
       const char *argv[i + 1];
       argv[0] = binpath;
-      memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
-      Environment environment;
-      start_program (argv, environment);
+      memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
+      start_program( argv, Environment() );
     }
 
 
-    ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
-                                 bool use_pty)
+    ExternalProgram::ExternalProgram( const char *binpath,
+                                     const char *const *argv_1,
+                                     const Environment & environment,
+                                     bool use_pty )
       : use_pty (use_pty)
+      , pid( -1 )
     {
       int i = 0;
       while (argv_1[i++])
        ;
       const char *argv[i + 1];
       argv[0] = binpath;
-      memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
-      start_program (argv, environment);
+      memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
+      start_program( argv, environment );
     }
 
 
     ExternalProgram::~ExternalProgram()
-    {
-    }
+    {}
 
 
-    void
-    ExternalProgram::start_program (const char *const *argv, const Environment & environment,
-                               Stderr_Disposition stderr_disp,
-                               int stderr_fd, bool default_locale, const char* root)
+
+    void ExternalProgram::start_program( const char *const *argv,
+                                        const Environment & environment,
+                                        Stderr_Disposition stderr_disp,
+                                        int stderr_fd,
+                                        bool default_locale,
+                                        const char * root )
     {
       pid = -1;
       _exitStatus = 0;
-      int to_external[2], from_external[2];  // fds for pair of pipes
-      int master_tty,  slave_tty;         // fds for pair of ttys
+      int to_external[2], from_external[2];    // fds for pair of pipes
+      int master_tty,  slave_tty;              // fds for pair of ttys
+
+      // retrieve options at beginning of arglist
+      const char * redirectStdin = nullptr;    // <[file]
+      const char * redirectStdout = nullptr;   // >[file]
+      const char * chdirTo = nullptr;          // #/[path]
+
+      if ( root )
+      {
+       if ( root[0] == '\0' )
+       {
+         root = nullptr;       // ignore empty root
+       }
+       else if ( root[0] == '/' && root[1] == '\0' )
+       {
+         // If root is '/' do not chroot, but chdir to '/'
+         // unless arglist defines another dir.
+         chdirTo = "/";
+         root = nullptr;
+       }
+      }
+
+      for ( bool strip = false; argv[0]; ++argv )
+      {
+       strip = false;
+       switch ( argv[0][0] )
+       {
+         case '<':
+           strip = true;
+           redirectStdin = argv[0]+1;
+           if ( *redirectStdin == '\0' )
+             redirectStdin = "/dev/null";
+           break;
+
+         case '>':
+           strip = true;
+           redirectStdout = argv[0]+1;
+           if ( *redirectStdout == '\0' )
+             redirectStdout = "/dev/null";
+           break;
+
+         case '#':
+           strip = true;
+           if ( argv[0][1] == '/' )    // #/[path]
+             chdirTo = argv[0]+1;
+           break;
+       }
+       if ( ! strip )
+         break;
+      }
+
+      // do not remove the single quotes around every argument, copy&paste of
+      // command to shell will not work otherwise!
+      {
+        stringstream cmdstr;
+        for (int i = 0; argv[i]; i++)
+        {
+          if (i>0) cmdstr << ' ';
+          cmdstr << '\'';
+          cmdstr << argv[i];
+          cmdstr << '\'';
+        }
+        if ( redirectStdin )
+          cmdstr << " < '" << redirectStdin << "'";
+        if ( redirectStdout )
+          cmdstr << " > '" << redirectStdout << "'";
+        _command = cmdstr.str();
+      }
+      DBG << "Executing " << _command << endl;
+
 
       if (use_pty)
       {
        // Create pair of ttys
-          DBG << "Using ttys for communication with " << argv[0] << endl;
+        DBG << "Using ttys for communication with " << argv[0] << endl;
        if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
        {
-           ERR << "openpty failed" << endl;
-           return;
+          _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
+          _exitStatus = 126;
+          ERR << _execError << endl;
+          return;
        }
       }
       else
@@ -151,29 +266,19 @@ namespace zypp {
        // Create pair of pipes
        if (pipe (to_external) != 0 || pipe (from_external) != 0)
        {
-           ERR << "pipe failed" << endl;
-           return;
+          _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
+          _exitStatus = 126;
+          ERR << _execError << endl;
+          return;
        }
       }
 
-      // do not remove the single quotes around every argument, copy&paste of
-      // command to shell will not work otherwise!
-
-      stringstream cmdstr;
-
-      cmdstr << "Executing ";
-      for (int i = 0; argv[i]; i++)
-      {
-       if (i>0) cmdstr << ' ';
-       cmdstr << '\'';
-       cmdstr << argv[i];
-       cmdstr << '\'';
-      }
-      DBG << cmdstr.str() << endl;
-
       // Create module process
       if ((pid = fork()) == 0)
       {
+        //////////////////////////////////////////////////////////////////////
+        // Don't write to the logfile after fork!
+        //////////////////////////////////////////////////////////////////////
        if (use_pty)
        {
            setsid();
@@ -199,6 +304,20 @@ namespace zypp {
            ::close(to_external  [1]);    // Belongs to father process
        }
 
+        if ( redirectStdin )
+        {
+          ::close( 0 );
+          int inp_fd = open( redirectStdin, O_RDONLY );
+          dup2( inp_fd, 0 );
+        }
+
+        if ( redirectStdout )
+        {
+          ::close( 1 );
+          int inp_fd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
+          dup2( inp_fd, 1 );
+        }
+
        // Handle stderr
        if (stderr_disp == Discard_Stderr)
        {
@@ -228,30 +347,42 @@ namespace zypp {
        {
            if(chroot(root) == -1)
            {
-               ERR << "chroot to " << root << " failed: " << strerror(errno) << endl;
-               _exit (3);                      // No sense in returning! I am forked away!!
-           }
-           if(chdir("/") == -1)
-           {
-               ERR << "chdir to / inside chroot failed: " << strerror(errno) << endl;
-               _exit (4);                      // No sense in returning! I am forked away!!
+                _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
+                std::cerr << _execError << endl;// After fork log on stderr too
+               _exit (128);                    // No sense in returning! I am forked away!!
            }
+           if ( ! chdirTo )
+             chdirTo = "/";
        }
 
+       if ( chdirTo && chdir( chdirTo ) == -1 )
+       {
+         _execError = root ? str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, root, strerror(errno) )
+                           : str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno) );
+         std::cerr << _execError << endl;// After fork log on stderr too
+         _exit (128);                  // No sense in returning! I am forked away!!
+       }
+
        // close all filedesctiptors above stderr
        for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
          ::close( i );
        }
 
        execvp(argv[0], const_cast<char *const *>(argv));
-       ERR << "Cannot execute external program "
-                << argv[0] << ":" << strerror(errno) << endl;
-       _exit (5);                      // No sense in returning! I am forked away!!
+        // don't want to get here
+        _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
+        std::cerr << _execError << endl;// After fork log on stderr too
+        _exit (129);                   // No sense in returning! I am forked away!!
+        //////////////////////////////////////////////////////////////////////
       }
 
       else if (pid == -1)       // Fork failed, close everything.
       {
-       if (use_pty) {
+        _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
+        _exitStatus = 127;
+        ERR << _execError << endl;
+
+       if (use_pty) {
            ::close(master_tty);
            ::close(slave_tty);
        }
@@ -261,7 +392,6 @@ namespace zypp {
            ::close(from_external[0]);
            ::close(from_external[1]);
        }
-       ERR << "Cannot fork " << strerror(errno) << endl;
       }
 
       else {
@@ -295,27 +425,74 @@ namespace zypp {
     {
       if (pid > 0)
       {
-       ExternalDataSource::close();
-       // Wait for child to exit
-       int ret;
-          int status = 0;
-       do
-       {
-           ret = waitpid(pid, &status, 0);
-       }
-       while (ret == -1 && errno == EINTR);
-
-       if (ret != -1)
-       {
-           status = checkStatus( status );
-       }
-          pid = -1;
-          return status;
-      }
-      else
-      {
-          return _exitStatus;
+       if ( inputFile() )
+       {
+         // Discard any output instead of closing the pipe,
+         // but watch out for the command exiting while some
+         // subprocess keeps the filedescriptor open.
+         setBlocking( false );
+         FILE * inputfile = inputFile();
+         int    inputfileFd = ::fileno( inputfile );
+         long   delay = 0;
+         do
+         {
+           /* Watch inputFile to see when it has input. */
+           fd_set rfds;
+           FD_ZERO( &rfds );
+           FD_SET( inputfileFd, &rfds );
+
+           /* Wait up to 1 seconds. */
+           struct timeval tv;
+           tv.tv_sec  = (delay < 0 ? 1 : 0);
+           tv.tv_usec = (delay < 0 ? 0 : delay*100000);
+           if ( delay >= 0 && ++delay > 9 )
+             delay = -1;
+           int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
+
+           if ( retval == -1 )
+           {
+             ERR << "select error: " << strerror(errno) << endl;
+             if ( errno != EINTR )
+               break;
+           }
+           else if ( retval )
+           {
+             // Data is available now.
+             static size_t linebuffer_size = 0;      // static because getline allocs
+             static char * linebuffer = 0;           // and reallocs if buffer is too small
+             getline( &linebuffer, &linebuffer_size, inputfile );
+             // ::feof check is important as select returns
+             // positive if the file was closed.
+             if ( ::feof( inputfile ) )
+               break;
+             clearerr( inputfile );
+           }
+           else
+           {
+             // No data within time.
+             if ( ! running() )
+               break;
+           }
+         } while ( true );
+       }
+
+       // Wait for child to exit
+       int ret;
+       int status = 0;
+       do
+       {
+         ret = waitpid(pid, &status, 0);
+       }
+       while (ret == -1 && errno == EINTR);
+
+       if (ret != -1)
+       {
+        _exitStatus = checkStatus( status );
+       }
+       pid = -1;
       }
+
+      return _exitStatus;
     }
 
 
@@ -326,29 +503,33 @@ namespace zypp {
        status = WEXITSTATUS (status);
        if(status)
        {
-           DBG << "pid " << pid << " exited with status " << status << endl;
+           DBG << "Pid " << pid << " exited with status " << status << endl;
+            _execError = str::form( _("Command exited with status %d."), status );
        }
        else
        {
            // if 'launch' is logged, completion should be logged,
            // even if successfull.
-           DBG << "pid " << pid << " successfully completed" << endl;
+           DBG << "Pid " << pid << " successfully completed" << endl;
+            _execError.clear(); // empty if running or successfully completed
        }
       }
       else if (WIFSIGNALED (status))
       {
        status = WTERMSIG (status);
-       WAR << "pid " << pid << " was killed by signal " << status
+       WAR << "Pid " << pid << " was killed by signal " << status
                << " (" << strsignal(status);
        if (WCOREDUMP (status))
        {
            WAR << ", core dumped";
        }
        WAR << ")" << endl;
+        _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
        status+=128;
       }
       else {
-       ERR << "pid " << pid << " exited with unknown error" << endl;
+       ERR << "Pid " << pid << " exited with unknown error" << endl;
+        _execError = _("Command exited with unknown error.");
       }
 
       return status;
@@ -403,4 +584,86 @@ namespace zypp {
       }
     }
 
+    std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
+    {
+      setBlocking( true );
+      for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
+        out_r << line;
+      return out_r;
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // class ExternalProgramWithStderr
+    //
+    //////////////////////////////////////////////////////////////////////
+
+    namespace _ExternalProgram
+    {
+      EarlyPipe::EarlyPipe()
+      {
+       _fds[R] = _fds[W] = -1;
+#ifdef HAVE_PIPE2
+       ::pipe2( _fds, O_NONBLOCK );
+#else
+        ::pipe( _fds );
+        ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
+        ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
+#endif
+       _stderr = ::fdopen( _fds[R], "r" );
+      }
+
+      EarlyPipe::~EarlyPipe()
+      {
+       closeW();
+       if ( _stderr )
+         ::fclose( _stderr );
+      }
+    }
+
+    bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
+    {
+      if ( ! _stderr )
+       return false;
+      if ( delim_r && ! _buffer.empty() )
+      {
+       // check for delim already in buffer
+       std::string::size_type pos( _buffer.find( delim_r ) );
+       if ( pos != std::string::npos )
+       {
+         retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
+         _buffer.erase( 0, pos+1 );
+         return true;
+       }
+      }
+      ::clearerr( _stderr );
+      do {
+       int ch = fgetc( _stderr );
+       if ( ch != EOF )
+       {
+         if ( ch != delim_r || ! delim_r )
+           _buffer.push_back( ch );
+         else
+         {
+           if ( returnDelim_r )
+             _buffer.push_back( delim_r );
+           break;
+         }
+       }
+       else if ( ::feof( _stderr ) )
+       {
+         if ( _buffer.empty() )
+           return false;
+         break;
+       }
+       else if ( errno != EINTR )
+         return false;
+      } while ( true );
+      // HERE: we left after readig at least one char (\n)
+      retval_r.swap( _buffer );
+      _buffer.clear();
+      return true;
+    }
+
+
 } // namespace zypp