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     {
00038     }
00039 
00040 
00041     ExternalProgram::ExternalProgram( std::string commandline,
00042                                       Stderr_Disposition stderr_disp,
00043                                       bool use_pty,
00044                                       int stderr_fd,
00045                                       bool default_locale,
00046                                       const Pathname & root )
00047     : use_pty (use_pty)
00048     {
00049       const char *argv[4];
00050       argv[0] = "/bin/sh";
00051       argv[1] = "-c";
00052       argv[2] = commandline.c_str();
00053       argv[3] = 0;
00054 
00055       const char* rootdir = NULL;
00056       if(!root.empty() && root != "/")
00057       {
00058         rootdir = root.asString().c_str();
00059       }
00060       Environment environment;
00061       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00062     }
00063 
00064 
00065     ExternalProgram::ExternalProgram (const Arguments &argv,
00066                                       Stderr_Disposition stderr_disp,
00067                                       bool use_pty, int stderr_fd,
00068                                       bool default_locale,
00069                                       const Pathname& root)
00070         : use_pty (use_pty)
00071     {
00072         const char * argvp[argv.size() + 1];
00073         unsigned c = 0;
00074         for_( i, argv.begin(), argv.end() )
00075         {
00076             argvp[c] = i->c_str();
00077             ++c;
00078         }
00079         argvp[c] = 0;
00080 
00081         Environment environment;
00082         const char* rootdir = NULL;
00083         if(!root.empty() && root != "/")
00084         {
00085             rootdir = root.asString().c_str();
00086         }
00087         start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00088     }
00089 
00090 
00091     ExternalProgram::ExternalProgram (const Arguments &argv,
00092                                       const Environment & environment,
00093                                       Stderr_Disposition stderr_disp,
00094                                       bool use_pty, int stderr_fd,
00095                                       bool default_locale,
00096                                       const Pathname& root)
00097         : use_pty (use_pty)
00098     {
00099         const char * argvp[argv.size() + 1];
00100         unsigned c = 0;
00101         for_( i, argv.begin(), argv.end() )
00102         {
00103             argvp[c] = i->c_str();
00104             ++c;
00105         }
00106         argvp[c] = 0;
00107 
00108         const char* rootdir = NULL;
00109         if(!root.empty() && root != "/")
00110         {
00111             rootdir = root.asString().c_str();
00112         }
00113         start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00114 
00115     }
00116 
00117 
00118 
00119 
00120     ExternalProgram::ExternalProgram( const char *const *argv,
00121                                       Stderr_Disposition stderr_disp,
00122                                       bool use_pty,
00123                                       int stderr_fd,
00124                                       bool default_locale,
00125                                       const Pathname & root )
00126     : use_pty (use_pty)
00127     {
00128       const char* rootdir = NULL;
00129       if(!root.empty() && root != "/")
00130       {
00131         rootdir = root.asString().c_str();
00132       }
00133       Environment environment;
00134       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00135     }
00136 
00137 
00138     ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
00139                                   Stderr_Disposition stderr_disp, bool use_pty,
00140                                   int stderr_fd, bool default_locale,
00141                                   const Pathname& root)
00142       : use_pty (use_pty)
00143     {
00144       const char* rootdir = NULL;
00145       if(!root.empty() && root != "/")
00146       {
00147         rootdir = root.asString().c_str();
00148       }
00149       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00150     }
00151 
00152 
00153     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
00154                                   bool use_pty)
00155       : use_pty (use_pty)
00156     {
00157       int i = 0;
00158       while (argv_1[i++])
00159         ;
00160       const char *argv[i + 1];
00161       argv[0] = binpath;
00162       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00163       Environment environment;
00164       start_program (argv, environment);
00165     }
00166 
00167 
00168     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
00169                                   bool use_pty)
00170       : use_pty (use_pty)
00171     {
00172       int i = 0;
00173       while (argv_1[i++])
00174         ;
00175       const char *argv[i + 1];
00176       argv[0] = binpath;
00177       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00178       start_program (argv, environment);
00179     }
00180 
00181 
00182     ExternalProgram::~ExternalProgram()
00183     {
00184     }
00185 
00186 
00187     void
00188     ExternalProgram::start_program (const char *const *argv, const Environment & environment,
00189                                 Stderr_Disposition stderr_disp,
00190                                 int stderr_fd, bool default_locale, const char* root)
00191     {
00192       pid = -1;
00193       _exitStatus = 0;
00194       int to_external[2], from_external[2];  // fds for pair of pipes
00195       int master_tty,   slave_tty;         // fds for pair of ttys
00196 
00197       const char * redirectStdin = 0;
00198       if ( argv[0] && *argv[0] == '<' )
00199       {
00200         redirectStdin = argv[0]+1;
00201         if ( *redirectStdin == '\0' )
00202           redirectStdin = "/dev/null";
00203         ++argv;
00204       }
00205 
00206       // do not remove the single quotes around every argument, copy&paste of
00207       // command to shell will not work otherwise!
00208       {
00209         stringstream cmdstr;
00210         for (int i = 0; argv[i]; i++)
00211         {
00212           if (i>0) cmdstr << ' ';
00213           cmdstr << '\'';
00214           cmdstr << argv[i];
00215           cmdstr << '\'';
00216         }
00217         if ( redirectStdin )
00218           cmdstr << " < '" << redirectStdin << "'";
00219         _command = cmdstr.str();
00220       }
00221       DBG << "Executing " << _command << endl;
00222 
00223 
00224       if (use_pty)
00225       {
00226         // Create pair of ttys
00227         DBG << "Using ttys for communication with " << argv[0] << endl;
00228         if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
00229         {
00230           _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
00231           _exitStatus = 126;
00232           ERR << _execError << endl;
00233           return;
00234         }
00235       }
00236       else
00237       {
00238         // Create pair of pipes
00239         if (pipe (to_external) != 0 || pipe (from_external) != 0)
00240         {
00241           _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
00242           _exitStatus = 126;
00243           ERR << _execError << endl;
00244           return;
00245         }
00246       }
00247 
00248       // Create module process
00249       if ((pid = fork()) == 0)
00250       {
00252         // Don't write to the logfile after fork!
00254         if (use_pty)
00255         {
00256             setsid();
00257             if(slave_tty != 1)
00258                 dup2 (slave_tty, 1);      // set new stdout
00259             renumber_fd (slave_tty, 0);   // set new stdin
00260     	    ::close(master_tty);     // Belongs to father process
00261 
00262             // We currently have no controlling terminal (due to setsid).
00263             // The first open call will also set the new ctty (due to historical
00264             // unix guru knowledge ;-) )
00265 
00266             char name[512];
00267             ttyname_r(slave_tty, name, sizeof(name));
00268     	    ::close(open(name, O_RDONLY));
00269         }
00270         else
00271         {
00272             renumber_fd (to_external[0], 0); // set new stdin
00273     	    ::close(from_external[0]);       // Belongs to father process
00274 
00275             renumber_fd (from_external[1], 1); // set new stdout
00276     	    ::close(to_external     [1]);    // Belongs to father process
00277         }
00278 
00279         if ( redirectStdin )
00280         {
00281           ::close( 0 );
00282           int inp_fd = open( redirectStdin, O_RDONLY );
00283           dup2( inp_fd, 0 );
00284         }
00285 
00286         // Handle stderr
00287         if (stderr_disp == Discard_Stderr)
00288         {
00289             int null_fd = open("/dev/null", O_WRONLY);
00290             dup2(null_fd, 2);
00291     	    ::close(null_fd);
00292         }
00293         else if (stderr_disp == Stderr_To_Stdout)
00294         {
00295             dup2(1, 2);
00296         }
00297         else if (stderr_disp == Stderr_To_FileDesc)
00298         {
00299             // Note: We don't have to close anything regarding stderr_fd.
00300             // Our caller is responsible for that.
00301             dup2 (stderr_fd, 2);
00302         }
00303 
00304         for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
00305           setenv( it->first.c_str(), it->second.c_str(), 1 );
00306         }
00307 
00308         if(default_locale)
00309                 setenv("LC_ALL","C",1);
00310 
00311         if(root)
00312         {
00313             if(chroot(root) == -1)
00314             {
00315                 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
00316                 std::cerr << _execError << endl;// After fork log on stderr too
00317                 _exit (128);                    // No sense in returning! I am forked away!!
00318             }
00319             if(chdir("/") == -1)
00320             {
00321                 _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
00322                 std::cerr << _execError << endl;// After fork log on stderr too
00323                 _exit (128);                    // No sense in returning! I am forked away!!
00324             }
00325         }
00326 
00327         // close all filedesctiptors above stderr
00328         for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
00329     	  ::close( i );
00330         }
00331 
00332         execvp(argv[0], const_cast<char *const *>(argv));
00333         // don't want to get here
00334         _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
00335         std::cerr << _execError << endl;// After fork log on stderr too
00336         _exit (129);                    // No sense in returning! I am forked away!!
00338       }
00339 
00340       else if (pid == -1)        // Fork failed, close everything.
00341       {
00342         _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
00343         _exitStatus = 127;
00344         ERR << _execError << endl;
00345 
00346         if (use_pty) {
00347     	    ::close(master_tty);
00348     	    ::close(slave_tty);
00349         }
00350         else {
00351     	    ::close(to_external[0]);
00352     	    ::close(to_external[1]);
00353     	    ::close(from_external[0]);
00354     	    ::close(from_external[1]);
00355         }
00356       }
00357 
00358       else {
00359         if (use_pty)
00360         {
00361     	    ::close(slave_tty);           // belongs to child process
00362             inputfile  = fdopen(master_tty, "r");
00363             outputfile = fdopen(master_tty, "w");
00364         }
00365         else
00366         {
00367     	    ::close(to_external[0]);   // belongs to child process
00368     	    ::close(from_external[1]); // belongs to child process
00369             inputfile = fdopen(from_external[0], "r");
00370             outputfile = fdopen(to_external[1], "w");
00371         }
00372 
00373         DBG << "pid " << pid << " launched" << endl;
00374 
00375         if (!inputfile || !outputfile)
00376         {
00377             ERR << "Cannot create streams to external program " << argv[0] << endl;
00378             close();
00379         }
00380       }
00381     }
00382 
00383 
00384     int
00385     ExternalProgram::close()
00386     {
00387       if (pid > 0)
00388       {
00389         setBlocking( true );
00390         while ( receiveLine().length() )
00391           ; // discard any output instead of closing the pipe
00392         //ExternalDataSource::close();
00393 
00394         // Wait for child to exit
00395         int ret;
00396         int status = 0;
00397         do
00398         {
00399             ret = waitpid(pid, &status, 0);
00400         }
00401         while (ret == -1 && errno == EINTR);
00402 
00403         if (ret != -1)
00404         {
00405             status = checkStatus( status );
00406         }
00407           pid = -1;
00408           return status;
00409       }
00410       else
00411       {
00412           return _exitStatus;
00413       }
00414     }
00415 
00416 
00417     int ExternalProgram::checkStatus( int status )
00418     {
00419       if (WIFEXITED (status))
00420       {
00421         status = WEXITSTATUS (status);
00422         if(status)
00423         {
00424             DBG << "Pid " << pid << " exited with status " << status << endl;
00425             _execError = str::form( _("Command exited with status %d."), status );
00426         }
00427         else
00428         {
00429             // if 'launch' is logged, completion should be logged,
00430             // even if successfull.
00431             DBG << "Pid " << pid << " successfully completed" << endl;
00432             //_execError = _("Command successfully completed.");
00433         }
00434       }
00435       else if (WIFSIGNALED (status))
00436       {
00437         status = WTERMSIG (status);
00438         WAR << "Pid " << pid << " was killed by signal " << status
00439                 << " (" << strsignal(status);
00440         if (WCOREDUMP (status))
00441         {
00442             WAR << ", core dumped";
00443         }
00444         WAR << ")" << endl;
00445         _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
00446         status+=128;
00447       }
00448       else {
00449         ERR << "Pid " << pid << " exited with unknown error" << endl;
00450         _execError = _("Command exited with unknown error.");
00451       }
00452 
00453       return status;
00454     }
00455 
00456     bool
00457     ExternalProgram::kill()
00458     {
00459       if (pid > 0)
00460       {
00461     	::kill(pid, SIGKILL);
00462         close();
00463       }
00464       return true;
00465     }
00466 
00467 
00468     bool
00469     ExternalProgram::running()
00470     {
00471       if ( pid < 0 ) return false;
00472 
00473       int status = 0;
00474       int p = waitpid( pid, &status, WNOHANG );
00475       switch ( p )
00476         {
00477         case -1:
00478           ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
00479           return false;
00480           break;
00481         case 0:
00482           return true; // still running
00483           break;
00484         }
00485 
00486       // Here: completed...
00487       _exitStatus = checkStatus( status );
00488       pid = -1;
00489       return false;
00490     }
00491 
00492     // origfd will be accessible as newfd and closed (unless they were equal)
00493     void ExternalProgram::renumber_fd (int origfd, int newfd)
00494     {
00495       // It may happen that origfd is already the one we want
00496       // (Although in our circumstances, that would mean somebody has closed
00497       // our stdin or stdout... weird but has appened to Cray, #49797)
00498       if (origfd != newfd)
00499       {
00500         dup2 (origfd, newfd);
00501     	::close (origfd);
00502       }
00503     }
00504 
00505     std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
00506     {
00507       setBlocking( true );
00508       for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
00509         out_r << line;
00510       return out_r;
00511     }
00512 
00513 
00514 } // namespace zypp
Generated on Fri Mar 2 09:45:51 2012 for libzypp by  doxygen 1.6.3