libzypp  10.5.0
ExternalProgram.cc
Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #define _GNU_SOURCE 1 // for ::getline
00013 
00014 #include <signal.h>
00015 #include <errno.h>
00016 #include <unistd.h>
00017 #include <sys/wait.h>
00018 #include <fcntl.h>
00019 #include <pty.h> // openpty
00020 #include <stdlib.h> // setenv
00021 
00022 #include <cstring> // strsignal
00023 #include <iostream>
00024 #include <sstream>
00025 
00026 #include "zypp/base/Logger.h"
00027 #include "zypp/base/String.h"
00028 #include "zypp/base/Gettext.h"
00029 #include "zypp/ExternalProgram.h"
00030 
00031 using namespace std;
00032 
00033 namespace zypp {
00034 
00035     ExternalProgram::ExternalProgram()
00036       : use_pty (false)
00037       , pid( -1 )
00038     {
00039     }
00040 
00041 
00042     ExternalProgram::ExternalProgram( std::string commandline,
00043                                       Stderr_Disposition stderr_disp,
00044                                       bool use_pty,
00045                                       int stderr_fd,
00046                                       bool default_locale,
00047                                       const Pathname & root )
00048       : use_pty (use_pty)
00049       , pid( -1 )
00050     {
00051       const char *argv[4];
00052       argv[0] = "/bin/sh";
00053       argv[1] = "-c";
00054       argv[2] = commandline.c_str();
00055       argv[3] = 0;
00056 
00057       const char* rootdir = NULL;
00058       if(!root.empty() && root != "/")
00059       {
00060         rootdir = root.asString().c_str();
00061       }
00062       Environment environment;
00063       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00064     }
00065 
00066 
00067     ExternalProgram::ExternalProgram (const Arguments &argv,
00068                                       Stderr_Disposition stderr_disp,
00069                                       bool use_pty, int stderr_fd,
00070                                       bool default_locale,
00071                                       const Pathname& root)
00072       : use_pty (use_pty)
00073       , pid( -1 )
00074     {
00075         const char * argvp[argv.size() + 1];
00076         unsigned c = 0;
00077         for_( i, argv.begin(), argv.end() )
00078         {
00079             argvp[c] = i->c_str();
00080             ++c;
00081         }
00082         argvp[c] = 0;
00083 
00084         Environment environment;
00085         const char* rootdir = NULL;
00086         if(!root.empty() && root != "/")
00087         {
00088             rootdir = root.asString().c_str();
00089         }
00090         start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00091     }
00092 
00093 
00094     ExternalProgram::ExternalProgram (const Arguments &argv,
00095                                       const Environment & environment,
00096                                       Stderr_Disposition stderr_disp,
00097                                       bool use_pty, int stderr_fd,
00098                                       bool default_locale,
00099                                       const Pathname& root)
00100       : use_pty (use_pty)
00101       , pid( -1 )
00102     {
00103         const char * argvp[argv.size() + 1];
00104         unsigned c = 0;
00105         for_( i, argv.begin(), argv.end() )
00106         {
00107             argvp[c] = i->c_str();
00108             ++c;
00109         }
00110         argvp[c] = 0;
00111 
00112         const char* rootdir = NULL;
00113         if(!root.empty() && root != "/")
00114         {
00115             rootdir = root.asString().c_str();
00116         }
00117         start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00118 
00119     }
00120 
00121 
00122 
00123 
00124     ExternalProgram::ExternalProgram( const char *const *argv,
00125                                       Stderr_Disposition stderr_disp,
00126                                       bool use_pty,
00127                                       int stderr_fd,
00128                                       bool default_locale,
00129                                       const Pathname & root )
00130       : use_pty (use_pty)
00131       , pid( -1 )
00132     {
00133       const char* rootdir = NULL;
00134       if(!root.empty() && root != "/")
00135       {
00136         rootdir = root.asString().c_str();
00137       }
00138       Environment environment;
00139       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00140     }
00141 
00142 
00143     ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
00144                                   Stderr_Disposition stderr_disp, bool use_pty,
00145                                   int stderr_fd, bool default_locale,
00146                                   const Pathname& root)
00147       : use_pty (use_pty)
00148       , pid( -1 )
00149     {
00150       const char* rootdir = NULL;
00151       if(!root.empty() && root != "/")
00152       {
00153         rootdir = root.asString().c_str();
00154       }
00155       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00156     }
00157 
00158 
00159     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
00160                                   bool use_pty)
00161       : use_pty (use_pty)
00162       , pid( -1 )
00163     {
00164       int i = 0;
00165       while (argv_1[i++])
00166         ;
00167       const char *argv[i + 1];
00168       argv[0] = binpath;
00169       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00170       Environment environment;
00171       start_program (argv, environment);
00172     }
00173 
00174 
00175     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
00176                                   bool use_pty)
00177       : use_pty (use_pty)
00178       , pid( -1 )
00179     {
00180       int i = 0;
00181       while (argv_1[i++])
00182         ;
00183       const char *argv[i + 1];
00184       argv[0] = binpath;
00185       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00186       start_program (argv, environment);
00187     }
00188 
00189 
00190     ExternalProgram::~ExternalProgram()
00191     {
00192     }
00193 
00194 
00195     void
00196     ExternalProgram::start_program (const char *const *argv, const Environment & environment,
00197                                 Stderr_Disposition stderr_disp,
00198                                 int stderr_fd, bool default_locale, const char* root)
00199     {
00200       pid = -1;
00201       _exitStatus = 0;
00202       int to_external[2], from_external[2];  // fds for pair of pipes
00203       int master_tty,   slave_tty;         // fds for pair of ttys
00204 
00205       const char * redirectStdin = 0;
00206       if ( argv[0] && *argv[0] == '<' )
00207       {
00208         redirectStdin = argv[0]+1;
00209         if ( *redirectStdin == '\0' )
00210           redirectStdin = "/dev/null";
00211         ++argv;
00212       }
00213 
00214       // do not remove the single quotes around every argument, copy&paste of
00215       // command to shell will not work otherwise!
00216       {
00217         stringstream cmdstr;
00218         for (int i = 0; argv[i]; i++)
00219         {
00220           if (i>0) cmdstr << ' ';
00221           cmdstr << '\'';
00222           cmdstr << argv[i];
00223           cmdstr << '\'';
00224         }
00225         if ( redirectStdin )
00226           cmdstr << " < '" << redirectStdin << "'";
00227         _command = cmdstr.str();
00228       }
00229       DBG << "Executing " << _command << endl;
00230 
00231 
00232       if (use_pty)
00233       {
00234         // Create pair of ttys
00235         DBG << "Using ttys for communication with " << argv[0] << endl;
00236         if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
00237         {
00238           _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
00239           _exitStatus = 126;
00240           ERR << _execError << endl;
00241           return;
00242         }
00243       }
00244       else
00245       {
00246         // Create pair of pipes
00247         if (pipe (to_external) != 0 || pipe (from_external) != 0)
00248         {
00249           _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
00250           _exitStatus = 126;
00251           ERR << _execError << endl;
00252           return;
00253         }
00254       }
00255 
00256       // Create module process
00257       if ((pid = fork()) == 0)
00258       {
00260         // Don't write to the logfile after fork!
00262         if (use_pty)
00263         {
00264             setsid();
00265             if(slave_tty != 1)
00266                 dup2 (slave_tty, 1);      // set new stdout
00267             renumber_fd (slave_tty, 0);   // set new stdin
00268             ::close(master_tty);          // Belongs to father process
00269 
00270             // We currently have no controlling terminal (due to setsid).
00271             // The first open call will also set the new ctty (due to historical
00272             // unix guru knowledge ;-) )
00273 
00274             char name[512];
00275             ttyname_r(slave_tty, name, sizeof(name));
00276             ::close(open(name, O_RDONLY));
00277         }
00278         else
00279         {
00280             renumber_fd (to_external[0], 0); // set new stdin
00281             ::close(from_external[0]);    // Belongs to father process
00282 
00283             renumber_fd (from_external[1], 1); // set new stdout
00284             ::close(to_external  [1]);    // Belongs to father process
00285         }
00286 
00287         if ( redirectStdin )
00288         {
00289           ::close( 0 );
00290           int inp_fd = open( redirectStdin, O_RDONLY );
00291           dup2( inp_fd, 0 );
00292         }
00293 
00294         // Handle stderr
00295         if (stderr_disp == Discard_Stderr)
00296         {
00297             int null_fd = open("/dev/null", O_WRONLY);
00298             dup2(null_fd, 2);
00299             ::close(null_fd);
00300         }
00301         else if (stderr_disp == Stderr_To_Stdout)
00302         {
00303             dup2(1, 2);
00304         }
00305         else if (stderr_disp == Stderr_To_FileDesc)
00306         {
00307             // Note: We don't have to close anything regarding stderr_fd.
00308             // Our caller is responsible for that.
00309             dup2 (stderr_fd, 2);
00310         }
00311 
00312         for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
00313           setenv( it->first.c_str(), it->second.c_str(), 1 );
00314         }
00315 
00316         if(default_locale)
00317                 setenv("LC_ALL","C",1);
00318 
00319         if(root)
00320         {
00321             if(chroot(root) == -1)
00322             {
00323                 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
00324                 std::cerr << _execError << endl;// After fork log on stderr too
00325                 _exit (128);                    // No sense in returning! I am forked away!!
00326             }
00327             if(chdir("/") == -1)
00328             {
00329                 _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
00330                 std::cerr << _execError << endl;// After fork log on stderr too
00331                 _exit (128);                    // No sense in returning! I am forked away!!
00332             }
00333         }
00334 
00335         // close all filedesctiptors above stderr
00336         for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
00337           ::close( i );
00338         }
00339 
00340         execvp(argv[0], const_cast<char *const *>(argv));
00341         // don't want to get here
00342         _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
00343         std::cerr << _execError << endl;// After fork log on stderr too
00344         _exit (129);                    // No sense in returning! I am forked away!!
00346       }
00347 
00348       else if (pid == -1)        // Fork failed, close everything.
00349       {
00350         _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
00351         _exitStatus = 127;
00352         ERR << _execError << endl;
00353 
00354         if (use_pty) {
00355             ::close(master_tty);
00356             ::close(slave_tty);
00357         }
00358         else {
00359             ::close(to_external[0]);
00360             ::close(to_external[1]);
00361             ::close(from_external[0]);
00362             ::close(from_external[1]);
00363         }
00364       }
00365 
00366       else {
00367         if (use_pty)
00368         {
00369             ::close(slave_tty);        // belongs to child process
00370             inputfile  = fdopen(master_tty, "r");
00371             outputfile = fdopen(master_tty, "w");
00372         }
00373         else
00374         {
00375             ::close(to_external[0]);   // belongs to child process
00376             ::close(from_external[1]); // belongs to child process
00377             inputfile = fdopen(from_external[0], "r");
00378             outputfile = fdopen(to_external[1], "w");
00379         }
00380 
00381         DBG << "pid " << pid << " launched" << endl;
00382 
00383         if (!inputfile || !outputfile)
00384         {
00385             ERR << "Cannot create streams to external program " << argv[0] << endl;
00386             close();
00387         }
00388       }
00389     }
00390 
00391 
00392     int
00393     ExternalProgram::close()
00394     {
00395       if (pid > 0)
00396       {
00397         if ( inputFile() )
00398         {
00399           // Discard any output instead of closing the pipe,
00400           // but watch out for the command exiting while some
00401           // subprocess keeps the filedescriptor open.
00402           setBlocking( false );
00403           FILE * inputfile = inputFile();
00404           int    inputfileFd = ::fileno( inputfile );
00405           long   delay = 0;
00406           do
00407           {
00408             /* Watch inputFile to see when it has input. */
00409             fd_set rfds;
00410             FD_ZERO( &rfds );
00411             FD_SET( inputfileFd, &rfds );
00412 
00413             /* Wait up to 1 seconds. */
00414             struct timeval tv;
00415             tv.tv_sec  = (delay < 0 ? 1 : 0);
00416             tv.tv_usec = (delay < 0 ? 0 : delay*100000);
00417             if ( delay >= 0 && ++delay > 9 )
00418               delay = -1;
00419             int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
00420 
00421             if ( retval == -1 )
00422             {
00423               ERR << "select error: " << strerror(errno) << endl;
00424               if ( errno != EINTR )
00425                 break;
00426             }
00427             else if ( retval )
00428             {
00429               // Data is available now.
00430               static size_t linebuffer_size = 0;      // static because getline allocs
00431               static char * linebuffer = 0;           // and reallocs if buffer is too small
00432               getline( &linebuffer, &linebuffer_size, inputfile );
00433               // ::feof check is important as select returns
00434               // positive if the file was closed.
00435               if ( ::feof( inputfile ) )
00436                 break;
00437               clearerr( inputfile );
00438             }
00439             else
00440             {
00441               // No data within time.
00442               if ( ! running() )
00443                 break;
00444             }
00445           } while ( true );
00446         }
00447 
00448         // Wait for child to exit
00449         int ret;
00450         int status = 0;
00451         do
00452         {
00453           ret = waitpid(pid, &status, 0);
00454         }
00455         while (ret == -1 && errno == EINTR);
00456 
00457         if (ret != -1)
00458         {
00459          _exitStatus = checkStatus( status );
00460         }
00461         pid = -1;
00462       }
00463 
00464       return _exitStatus;
00465     }
00466 
00467 
00468     int ExternalProgram::checkStatus( int status )
00469     {
00470       if (WIFEXITED (status))
00471       {
00472         status = WEXITSTATUS (status);
00473         if(status)
00474         {
00475             DBG << "Pid " << pid << " exited with status " << status << endl;
00476             _execError = str::form( _("Command exited with status %d."), status );
00477         }
00478         else
00479         {
00480             // if 'launch' is logged, completion should be logged,
00481             // even if successfull.
00482             DBG << "Pid " << pid << " successfully completed" << endl;
00483             _execError.clear(); // empty if running or successfully completed
00484         }
00485       }
00486       else if (WIFSIGNALED (status))
00487       {
00488         status = WTERMSIG (status);
00489         WAR << "Pid " << pid << " was killed by signal " << status
00490                 << " (" << strsignal(status);
00491         if (WCOREDUMP (status))
00492         {
00493             WAR << ", core dumped";
00494         }
00495         WAR << ")" << endl;
00496         _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
00497         status+=128;
00498       }
00499       else {
00500         ERR << "Pid " << pid << " exited with unknown error" << endl;
00501         _execError = _("Command exited with unknown error.");
00502       }
00503 
00504       return status;
00505     }
00506 
00507     bool
00508     ExternalProgram::kill()
00509     {
00510       if (pid > 0)
00511       {
00512         ::kill(pid, SIGKILL);
00513         close();
00514       }
00515       return true;
00516     }
00517 
00518 
00519     bool
00520     ExternalProgram::running()
00521     {
00522       if ( pid < 0 ) return false;
00523 
00524       int status = 0;
00525       int p = waitpid( pid, &status, WNOHANG );
00526       switch ( p )
00527         {
00528         case -1:
00529           ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
00530           return false;
00531           break;
00532         case 0:
00533           return true; // still running
00534           break;
00535         }
00536 
00537       // Here: completed...
00538       _exitStatus = checkStatus( status );
00539       pid = -1;
00540       return false;
00541     }
00542 
00543     // origfd will be accessible as newfd and closed (unless they were equal)
00544     void ExternalProgram::renumber_fd (int origfd, int newfd)
00545     {
00546       // It may happen that origfd is already the one we want
00547       // (Although in our circumstances, that would mean somebody has closed
00548       // our stdin or stdout... weird but has appened to Cray, #49797)
00549       if (origfd != newfd)
00550       {
00551         dup2 (origfd, newfd);
00552         ::close (origfd);
00553       }
00554     }
00555 
00556     std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
00557     {
00558       setBlocking( true );
00559       for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
00560         out_r << line;
00561       return out_r;
00562     }
00563 
00565     //
00566     // class ExternalProgramWithStderr
00567     //
00569 
00570     namespace _ExternalProgram
00571     {
00572       EarlyPipe::EarlyPipe()
00573       {
00574         _fds[R] = _fds[W] = -1;
00575 #ifdef HAVE_PIPE2
00576         ::pipe2( _fds, O_NONBLOCK );
00577 #else
00578         ::pipe( _fds );
00579         ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
00580         ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
00581 #endif
00582         _stderr = ::fdopen( _fds[R], "r" );
00583       }
00584 
00585       EarlyPipe::~EarlyPipe()
00586       {
00587         closeW();
00588         if ( _stderr )
00589           ::fclose( _stderr );
00590       }
00591     }
00592 
00593     bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
00594     {
00595       if ( ! _stderr )
00596         return false;
00597       if ( delim_r && ! _buffer.empty() )
00598       {
00599         // check for delim already in buffer
00600         std::string::size_type pos( _buffer.find( delim_r ) );
00601         if ( pos != std::string::npos )
00602         {
00603           retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
00604           _buffer.erase( 0, pos+1 );
00605           return true;
00606         }
00607       }
00608       ::clearerr( _stderr );
00609       do {
00610         int ch = fgetc( _stderr );
00611         if ( ch != EOF )
00612         {
00613           if ( ch != delim_r || ! delim_r )
00614             _buffer.push_back( ch );
00615           else
00616           {
00617             if ( returnDelim_r )
00618               _buffer.push_back( delim_r );
00619             break;
00620           }
00621         }
00622         else if ( ::feof( _stderr ) )
00623         {
00624           if ( _buffer.empty() )
00625             return false;
00626           break;
00627         }
00628         else if ( errno != EINTR )
00629           return false;
00630       } while ( true );
00631       // HERE: we left after readig at least one char (\n)
00632       retval_r.swap( _buffer );
00633       _buffer.clear();
00634       return true;
00635     }
00636 
00637 
00638 } // namespace zypp