ExternalProgram.cc

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

Generated on Thu Jul 6 00:07:21 2006 for zypp by  doxygen 1.4.6