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/ExternalProgram.h"
00028 
00029 using namespace std;
00030 
00031 namespace zypp {
00032 
00033     ExternalProgram::ExternalProgram()
00034     : use_pty (false)
00035     {
00036     }
00037 
00038     ExternalProgram::ExternalProgram( std::string commandline,
00039                                       Stderr_Disposition stderr_disp,
00040                                       bool use_pty,
00041                                       int stderr_fd,
00042                                       bool default_locale,
00043                                       const Pathname & root )
00044     : use_pty (use_pty)
00045     {
00046       const char *argv[4];
00047       argv[0] = "/bin/sh";
00048       argv[1] = "-c";
00049       argv[2] = commandline.c_str();
00050       argv[3] = 0;
00051 
00052       const char* rootdir = NULL;
00053       if(!root.empty() && root != "/")
00054       {
00055         rootdir = root.asString().c_str();
00056       }
00057       Environment environment;
00058       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00059     }
00060 
00061 
00062     ExternalProgram::ExternalProgram( const char *const *argv,
00063                                       Stderr_Disposition stderr_disp,
00064                                       bool use_pty,
00065                                       int stderr_fd,
00066                                       bool default_locale,
00067                                       const Pathname & root )
00068     : use_pty (use_pty)
00069     {
00070       const char* rootdir = NULL;
00071       if(!root.empty() && root != "/")
00072       {
00073         rootdir = root.asString().c_str();
00074       }
00075       Environment environment;
00076       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00077     }
00078 
00079 
00080     ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
00081                                   Stderr_Disposition stderr_disp, bool use_pty,
00082                                   int stderr_fd, bool default_locale,
00083                                   const Pathname& root)
00084       : use_pty (use_pty)
00085     {
00086       const char* rootdir = NULL;
00087       if(!root.empty() && root != "/")
00088       {
00089         rootdir = root.asString().c_str();
00090       }
00091       start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
00092     }
00093 
00094 
00095     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
00096                                   bool use_pty)
00097       : use_pty (use_pty)
00098     {
00099       int i = 0;
00100       while (argv_1[i++])
00101         ;
00102       const char *argv[i + 1];
00103       argv[0] = binpath;
00104       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00105       Environment environment;
00106       start_program (argv, environment);
00107     }
00108 
00109 
00110     ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
00111                                   bool use_pty)
00112       : use_pty (use_pty)
00113     {
00114       int i = 0;
00115       while (argv_1[i++])
00116         ;
00117       const char *argv[i + 1];
00118       argv[0] = binpath;
00119       memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
00120       start_program (argv, environment);
00121     }
00122 
00123 
00124     ExternalProgram::~ExternalProgram()
00125     {
00126     }
00127 
00128 
00129     void
00130     ExternalProgram::start_program (const char *const *argv, const Environment & environment,
00131                                 Stderr_Disposition stderr_disp,
00132                                 int stderr_fd, bool default_locale, const char* root)
00133     {
00134       pid = -1;
00135       _exitStatus = 0;
00136       int to_external[2], from_external[2];  // fds for pair of pipes
00137       int master_tty,   slave_tty;         // fds for pair of ttys
00138 
00139       if (use_pty)
00140       {
00141         // Create pair of ttys
00142           DBG << "Using ttys for communication with " << argv[0] << endl;
00143         if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
00144         {
00145             ERR << "openpty failed" << endl;
00146             return;
00147         }
00148       }
00149       else
00150       {
00151         // Create pair of pipes
00152         if (pipe (to_external) != 0 || pipe (from_external) != 0)
00153         {
00154             ERR << "pipe failed" << endl;
00155             return;
00156         }
00157       }
00158 
00159       // do not remove the single quotes around every argument, copy&paste of
00160       // command to shell will not work otherwise!
00161 
00162       stringstream cmdstr;
00163 
00164       cmdstr << "Executing ";
00165       for (int i = 0; argv[i]; i++)
00166       {
00167         if (i>0) cmdstr << ' ';
00168         cmdstr << '\'';
00169         cmdstr << argv[i];
00170         cmdstr << '\'';
00171       }
00172       DBG << cmdstr.str() << endl;
00173 
00174       // Create module process
00175       if ((pid = fork()) == 0)
00176       {
00177         if (use_pty)
00178         {
00179             setsid();
00180             if(slave_tty != 1)
00181                 dup2 (slave_tty, 1);      // set new stdout
00182             renumber_fd (slave_tty, 0);   // set new stdin
00183     	    ::close(master_tty);     // Belongs to father process
00184 
00185             // We currently have no controlling terminal (due to setsid).
00186             // The first open call will also set the new ctty (due to historical
00187             // unix guru knowledge ;-) )
00188 
00189             char name[512];
00190             ttyname_r(slave_tty, name, sizeof(name));
00191     	    ::close(open(name, O_RDONLY));
00192         }
00193         else
00194         {
00195             renumber_fd (to_external[0], 0); // set new stdin
00196     	    ::close(from_external[0]);       // Belongs to father process
00197 
00198             renumber_fd (from_external[1], 1); // set new stdout
00199     	    ::close(to_external     [1]);    // Belongs to father process
00200         }
00201 
00202         // Handle stderr
00203         if (stderr_disp == Discard_Stderr)
00204         {
00205             int null_fd = open("/dev/null", O_WRONLY);
00206             dup2(null_fd, 2);
00207     	    ::close(null_fd);
00208         }
00209         else if (stderr_disp == Stderr_To_Stdout)
00210         {
00211             dup2(1, 2);
00212         }
00213         else if (stderr_disp == Stderr_To_FileDesc)
00214         {
00215             // Note: We don't have to close anything regarding stderr_fd.
00216             // Our caller is responsible for that.
00217             dup2 (stderr_fd, 2);
00218         }
00219 
00220         for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
00221           setenv( it->first.c_str(), it->second.c_str(), 1 );
00222         }
00223 
00224         if(default_locale)
00225                 setenv("LC_ALL","C",1);
00226 
00227         if(root)
00228         {
00229             if(chroot(root) == -1)
00230             {
00231                 ERR << "chroot to " << root << " failed: " << strerror(errno) << endl;
00232                 _exit (3);                      // No sense in returning! I am forked away!!
00233             }
00234             if(chdir("/") == -1)
00235             {
00236                 ERR << "chdir to / inside chroot failed: " << strerror(errno) << endl;
00237                 _exit (4);                      // No sense in returning! I am forked away!!
00238             }
00239         }
00240 
00241         // close all filedesctiptors above stderr
00242         for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
00243     	  ::close( i );
00244         }
00245 
00246         execvp(argv[0], const_cast<char *const *>(argv));
00247         ERR << "Cannot execute external program "
00248                  << argv[0] << ":" << strerror(errno) << endl;
00249         _exit (5);                      // No sense in returning! I am forked away!!
00250       }
00251 
00252       else if (pid == -1)        // Fork failed, close everything.
00253       {
00254         if (use_pty) {
00255     	    ::close(master_tty);
00256     	    ::close(slave_tty);
00257         }
00258         else {
00259     	    ::close(to_external[0]);
00260     	    ::close(to_external[1]);
00261     	    ::close(from_external[0]);
00262     	    ::close(from_external[1]);
00263         }
00264         ERR << "Cannot fork " << strerror(errno) << endl;
00265         _exitStatus = -127;
00266       }
00267 
00268       else {
00269         if (use_pty)
00270         {
00271     	    ::close(slave_tty);           // belongs to child process
00272             inputfile  = fdopen(master_tty, "r");
00273             outputfile = fdopen(master_tty, "w");
00274         }
00275         else
00276         {
00277     	    ::close(to_external[0]);   // belongs to child process
00278     	    ::close(from_external[1]); // belongs to child process
00279             inputfile = fdopen(from_external[0], "r");
00280             outputfile = fdopen(to_external[1], "w");
00281         }
00282 
00283         DBG << "pid " << pid << " launched" << endl;
00284 
00285         if (!inputfile || !outputfile)
00286         {
00287             ERR << "Cannot create streams to external program " << argv[0] << endl;
00288             close();
00289         }
00290       }
00291     }
00292 
00293 
00294     int
00295     ExternalProgram::close()
00296     {
00297       if (pid > 0)
00298       {
00299         ExternalDataSource::close();
00300         // Wait for child to exit
00301         int ret;
00302           int status = 0;
00303         do
00304         {
00305             ret = waitpid(pid, &status, 0);
00306         }
00307         while (ret == -1 && errno == EINTR);
00308 
00309         if (ret != -1)
00310         {
00311             status = checkStatus( status );
00312         }
00313           pid = -1;
00314           return status;
00315       }
00316       else
00317       {
00318           return _exitStatus;
00319       }
00320     }
00321 
00322 
00323     int ExternalProgram::checkStatus( int status )
00324     {
00325       if (WIFEXITED (status))
00326       {
00327         status = WEXITSTATUS (status);
00328         if(status)
00329         {
00330             DBG << "pid " << pid << " exited with status " << status << endl;
00331         }
00332         else
00333         {
00334             // if 'launch' is logged, completion should be logged,
00335             // even if successfull.
00336             DBG << "pid " << pid << " successfully completed" << endl;
00337         }
00338       }
00339       else if (WIFSIGNALED (status))
00340       {
00341         status = WTERMSIG (status);
00342         WAR << "pid " << pid << " was killed by signal " << status
00343                 << " (" << strsignal(status);
00344         if (WCOREDUMP (status))
00345         {
00346             WAR << ", core dumped";
00347         }
00348         WAR << ")" << endl;
00349         status+=128;
00350       }
00351       else {
00352         ERR << "pid " << pid << " exited with unknown error" << endl;
00353       }
00354 
00355       return status;
00356     }
00357 
00358     bool
00359     ExternalProgram::kill()
00360     {
00361       if (pid > 0)
00362       {
00363     	::kill(pid, SIGKILL);
00364         close();
00365       }
00366       return true;
00367     }
00368 
00369 
00370     bool
00371     ExternalProgram::running()
00372     {
00373       if ( pid < 0 ) return false;
00374 
00375       int status = 0;
00376       int p = waitpid( pid, &status, WNOHANG );
00377       switch ( p )
00378         {
00379         case -1:
00380           ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
00381           return false;
00382           break;
00383         case 0:
00384           return true; // still running
00385           break;
00386         }
00387 
00388       // Here: completed...
00389       _exitStatus = checkStatus( status );
00390       pid = -1;
00391       return false;
00392     }
00393 
00394     // origfd will be accessible as newfd and closed (unless they were equal)
00395     void ExternalProgram::renumber_fd (int origfd, int newfd)
00396     {
00397       // It may happen that origfd is already the one we want
00398       // (Although in our circumstances, that would mean somebody has closed
00399       // our stdin or stdout... weird but has appened to Cray, #49797)
00400       if (origfd != newfd)
00401       {
00402         dup2 (origfd, newfd);
00403     	::close (origfd);
00404       }
00405     }
00406 
00407 } // namespace zypp

Generated on Tue Sep 25 19:23:01 2007 for libzypp by  doxygen 1.5.3