Imported Upstream version 17.22.1
[platform/upstream/libzypp.git] / zypp / ExternalProgram.cc
index 22979d0..3a38f04 100644 (file)
@@ -18,6 +18,7 @@
 #include <fcntl.h>
 #include <pty.h> // openpty
 #include <stdlib.h> // setenv
+#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
 
 #include <cstring> // strsignal
 #include <iostream>
 #include "zypp/base/String.h"
 #include "zypp/base/Gettext.h"
 #include "zypp/ExternalProgram.h"
+#include "zypp/base/CleanerThread_p.h"
 
 using namespace std;
 
+#undef  ZYPP_BASE_LOGGER_LOGGROUP
+#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
+
 namespace zypp {
 
     ExternalProgram::ExternalProgram()
-    : use_pty (false)
-    {
-    }
+      : use_pty (false)
+      , pid( -1 )
+    {}
 
 
     ExternalProgram::ExternalProgram( std::string commandline,
@@ -44,7 +49,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";
@@ -52,147 +58,187 @@ 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 Arguments &argv,
+    ExternalProgram::ExternalProgram( const Arguments & argv,
                                       Stderr_Disposition stderr_disp,
-                                      bool use_pty, int stderr_fd,
+                                      bool use_pty,
+                                     int stderr_fd,
                                       bool default_locale,
-                                      const Pathname& root)
-        : use_pty (use_pty)
+                                      const Pathname & root )
+      : use_pty (use_pty)
+      , pid( -1 )
     {
-        const char * argvp[argv.size() + 1];
-        unsigned c = 0;
-        for_( i, argv.begin(), argv.end() )
-        {
-            argvp[c] = i->c_str();
-            ++c;
-        }
-        argvp[c] = 0;
+      const char * argvp[argv.size() + 1];
+      unsigned c = 0;
+      for_( i, argv.begin(), argv.end() )
+      {
+       argvp[c] = i->c_str();
+       ++c;
+      }
+      argvp[c] = 0;
 
-        Environment environment;
-        const char* rootdir = NULL;
-        if(!root.empty() && root != "/")
-        {
-            rootdir = root.asString().c_str();
-        }
-        start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+      start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
     }
 
 
-    ExternalProgram::ExternalProgram (const Arguments &argv,
+    ExternalProgram::ExternalProgram( const Arguments & argv,
                                       const Environment & environment,
                                       Stderr_Disposition stderr_disp,
-                                      bool use_pty, int stderr_fd,
+                                      bool use_pty,
+                                     int stderr_fd,
                                       bool default_locale,
-                                      const Pathname& root)
-        : use_pty (use_pty)
+                                     const Pathname & root )
+      : use_pty (use_pty)
+      , pid( -1 )
     {
-        const char * argvp[argv.size() + 1];
-        unsigned c = 0;
-        for_( i, argv.begin(), argv.end() )
-        {
-            argvp[c] = i->c_str();
-            ++c;
-        }
-        argvp[c] = 0;
-
-        const char* rootdir = NULL;
-        if(!root.empty() && root != "/")
-        {
-            rootdir = root.asString().c_str();
-        }
-        start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+      const char * argvp[argv.size() + 1];
+      unsigned c = 0;
+      for_( i, argv.begin(), argv.end() )
+      {
+       argvp[c] = i->c_str();
+       ++c;
+      }
+      argvp[c] = 0;
 
+      start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
     }
 
 
 
-
     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)
+      : use_pty (use_pty)
+      , pid( -1 )
     {
-      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, const Environment & environment,
-                                 Stderr_Disposition stderr_disp, bool use_pty,
-                                 int stderr_fd, bool default_locale,
-                                 const Pathname& root)
+    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 )
     {
-      const char* rootdir = NULL;
-      if(!root.empty() && root != "/")
-      {
-       rootdir = root.asString().c_str();
-      }
-      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 *binpath, const char *const *argv_1,
-                                 bool use_pty)
+    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()
     {
+      if ( running() ) {
+        // we got destructed while the external process is still alive
+        // make sure the zombie is cleaned up once it exits
+        CleanerThread::watchPID( pid );
+      }
     }
 
 
-    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 , bool switch_pgid, bool die_with_parent )
     {
       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!
@@ -205,9 +251,13 @@ namespace zypp {
           cmdstr << argv[i];
           cmdstr << '\'';
         }
+        if ( redirectStdin )
+          cmdstr << " < '" << redirectStdin << "'";
+        if ( redirectStdout )
+          cmdstr << " > '" << redirectStdout << "'";
         _command = cmdstr.str();
       }
-      DBG << "Executing " << _command << endl;
+      DBG << "Executing" << (default_locale?"[C] ":" ") << _command << endl;
 
 
       if (use_pty)
@@ -234,9 +284,14 @@ namespace zypp {
        }
       }
 
+      pid_t ppid_before_fork = ::getpid();
+
       // Create module process
       if ((pid = fork()) == 0)
       {
+        //////////////////////////////////////////////////////////////////////
+        // Don't write to the logfile after fork!
+        //////////////////////////////////////////////////////////////////////
        if (use_pty)
        {
            setsid();
@@ -255,6 +310,8 @@ namespace zypp {
        }
        else
        {
+            if ( switch_pgid )
+              setpgid( 0, 0);
            renumber_fd (to_external[0], 0); // set new stdin
            ::close(from_external[0]);    // Belongs to father process
 
@@ -262,6 +319,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)
        {
@@ -292,30 +363,49 @@ namespace zypp {
            if(chroot(root) == -1)
            {
                 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
-                ERR << _execError << endl;
-                std::cerr << _execError << endl;// After fork log on stderr too
-               _exit (128);                    // No sense in returning! I am forked away!!
-           }
-           if(chdir("/") == -1)
-           {
-                _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
-                ERR << _execError << endl;
                 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 );
        }
 
+        if ( die_with_parent ) {
+          // process dies with us
+          int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
+          if (r == -1) {
+            //ignore if it did not work, worst case the process lives on after the parent dies
+            std::cerr << "Failed to set PR_SET_PDEATHSIG" << endl;// After fork log on stderr too
+          }
+
+          // test in case the original parent exited just
+          // before the prctl() call
+          pid_t ppidNow = getppid();
+          if (ppidNow != ppid_before_fork) {
+            std::cerr << "PPID changed from "<<ppid_before_fork<<" to "<< ppidNow << endl;// After fork log on stderr too
+            _exit(128);
+          }
+        }
+
        execvp(argv[0], const_cast<char *const *>(argv));
         // don't want to get here
         _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
-        ERR << _execError << endl;
         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.
@@ -367,31 +457,77 @@ namespace zypp {
     {
       if (pid > 0)
       {
-        setBlocking( true );
-        while ( receiveLine().length() )
-          ; // discard any output instead of closing the pipe
-       //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 );
+       }
+
+       if ( pid > 0 )  // bsc#1109877: must re-check! running() in the loop above may have already waited.
+       {
+         // 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;
     }
 
 
@@ -410,7 +546,7 @@ namespace zypp {
            // if 'launch' is logged, completion should be logged,
            // even if successfull.
            DBG << "Pid " << pid << " successfully completed" << endl;
-            //_execError = _("Command successfully completed.");
+            _execError.clear(); // empty if running or successfully completed
        }
       }
       else if (WIFSIGNALED (status))
@@ -445,6 +581,14 @@ namespace zypp {
       return true;
     }
 
+    bool ExternalProgram::kill(int sig)
+    {
+      if (pid > 0)
+      {
+        ::kill(pid, sig);
+      }
+      return true;
+    }
 
     bool
     ExternalProgram::running()
@@ -491,5 +635,78 @@ namespace zypp {
       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 );
+      }
+    } // namespace externalprogram
+
+    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