PluginScript.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #include <sys/types.h>
00013 #include <signal.h>
00014 
00015 #include <iostream>
00016 #include <sstream>
00017 
00018 #include "zypp/base/LogTools.h"
00019 #include "zypp/base/DefaultIntegral.h"
00020 #include "zypp/base/String.h"
00021 #include "zypp/base/Signal.h"
00022 #include "zypp/base/IOStream.h"
00023 
00024 #include "zypp/PluginScript.h"
00025 #include "zypp/ExternalProgram.h"
00026 #include "zypp/PathInfo.h"
00027 
00028 using std::endl;
00029 
00031 namespace zypp
00032 { 
00033 
00034 
00035   namespace
00036   {
00037     const char * PLUGIN_DEBUG = getenv( "ZYPP_PLUGIN_DEBUG" );
00038     const long PLUGIN_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_TIMEOUT" ) );
00039     const long PLUGIN_SEND_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_SEND_TIMEOUT" ) );
00040     const long PLUGIN_RECEIVE_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_RECEIVE_TIMEOUT" ) );
00041 
00045     struct PluginDebugBuffer
00046     {
00047       PluginDebugBuffer( const std::string & buffer_r ) : _buffer( buffer_r ) {}
00048       ~PluginDebugBuffer()
00049       {
00050         if ( PLUGIN_DEBUG )
00051         {
00052           if ( _buffer.empty() )
00053           {
00054             _DBG("PLUGIN") << "< (empty)" << endl;
00055           }
00056           else
00057           {
00058             std::istringstream datas( _buffer );
00059             iostr::copyIndent( datas, _DBG("PLUGIN"), "< "  ) << endl;
00060           }
00061         }
00062       }
00063       const std::string & _buffer;
00064     };
00065 
00069     struct PluginDumpStderr
00070     {
00071       PluginDumpStderr( ExternalProgramWithStderr & prog_r ) : _prog( prog_r ) {}
00072       ~PluginDumpStderr()
00073       {
00074         std::string line;
00075         while ( _prog.stderrGetline( line ) )
00076           _WAR("PLUGIN") << "! " << line << endl;
00077       }
00078       ExternalProgramWithStderr & _prog;
00079     };
00080 
00081     inline void setBlocking( FILE * file_r, bool yesno_r = true )
00082     {
00083       if ( ! file_r )
00084         ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
00085 
00086       int fd = ::fileno( file_r );
00087       if ( fd == -1 )
00088         ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
00089 
00090       int flags = ::fcntl( fd, F_GETFL );
00091       if ( flags == -1 )
00092         ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
00093 
00094       if ( ! yesno_r )
00095         flags |= O_NONBLOCK;
00096       else if ( flags & O_NONBLOCK )
00097         flags ^= O_NONBLOCK;
00098 
00099       flags = ::fcntl( fd, F_SETFL, flags );
00100       if ( flags == -1 )
00101         ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
00102     }
00103 
00104     inline void setNonBlocking( FILE * file_r, bool yesno_r = true )
00105     { setBlocking( file_r, !yesno_r ); }
00106   }
00107 
00109   //
00110   //    CLASS NAME : PluginScript::Impl
00111   //
00113   struct PluginScript::Impl
00114   {
00115     public:
00116       Impl( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() )
00117         : _script( script_r )
00118         , _args( args_r )
00119       {}
00120 
00121       ~ Impl()
00122       { try { close(); } catch(...) {} }
00123 
00124     public:
00126       static const long send_timeout;
00128       static const long receive_timeout;
00129 
00130     public:
00131       const Pathname & script() const
00132       { return _script; }
00133 
00134       const Arguments & args() const
00135       { return _args; }
00136 
00137       pid_t getPid() const
00138       { return _cmd ? _cmd->getpid() : NotConnected; }
00139 
00140       bool isOpen() const
00141       { return _cmd; }
00142 
00143       int lastReturn() const
00144       { return _lastReturn; }
00145 
00146       const std::string & lastExecError() const
00147       { return _lastExecError; }
00148 
00149     public:
00150       void open( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() );
00151 
00152       int close();
00153 
00154       void send( const PluginFrame & frame_r ) const;
00155 
00156       PluginFrame receive() const;
00157 
00158     private:
00159       Pathname _script;
00160       Arguments _args;
00161       scoped_ptr<ExternalProgramWithStderr> _cmd;
00162       DefaultIntegral<int,0> _lastReturn;
00163       std::string _lastExecError;
00164   };
00166 
00168   inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj )
00169   {
00170     return dumpRangeLine( str << "PluginScript[" << obj.getPid() << "] " << obj.script(),
00171                           obj.args().begin(), obj.args().end() );
00172   }
00173 
00175 
00176   const long PluginScript::Impl::send_timeout = ( PLUGIN_SEND_TIMEOUT > 0 ? PLUGIN_SEND_TIMEOUT
00177                                                                           : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
00178   const long PluginScript::Impl::receive_timeout = ( PLUGIN_RECEIVE_TIMEOUT > 0 ? PLUGIN_RECEIVE_TIMEOUT
00179                                                                                 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
00180 
00182 
00183   void PluginScript::Impl::open( const Pathname & script_r, const Arguments & args_r )
00184   {
00185     dumpRangeLine( DBG << "Open " << script_r, args_r.begin(), args_r.end() ) << endl;
00186 
00187     if ( _cmd )
00188       ZYPP_THROW( PluginScriptException( "Already connected", str::Str() << *this ) );
00189 
00190     {
00191       PathInfo pi( script_r );
00192       if ( ! ( pi.isFile() && pi.isX() ) )
00193         ZYPP_THROW( PluginScriptException( "Script is not executable", str::Str() << pi ) );
00194     }
00195 
00196     // go and launch script
00197     // TODO: ExternalProgram::maybe use Stderr_To_FileDesc for script loging
00198     Arguments args;
00199     args.reserve( args_r.size()+1 );
00200     args.push_back( script_r.asString() );
00201     args.insert( args.end(), args_r.begin(), args_r.end() );
00202     _cmd.reset( new ExternalProgramWithStderr( args ) );
00203 
00204     // Be protected against full pipe, etc.
00205     setNonBlocking( _cmd->outputFile() );
00206     setNonBlocking( _cmd->inputFile() );
00207 
00208     // store running scripts data
00209     _script = script_r;
00210     _args = args_r;
00211     _lastReturn.reset();
00212     _lastExecError.clear();
00213 
00214     DBG << *this << endl;
00215   }
00216 
00217   int PluginScript::Impl::close()
00218   {
00219     if ( _cmd )
00220     {
00221       DBG << "Close:" << *this << endl;
00222       _cmd->kill();
00223       _lastReturn = _cmd->close();
00224       _lastExecError = _cmd->execError();
00225       _cmd.reset();
00226       DBG << *this << " -> [" << _lastReturn << "] " << _lastExecError << endl;
00227     }
00228     return _lastReturn;
00229   }
00230 
00231   void PluginScript::Impl::send( const PluginFrame & frame_r ) const
00232   {
00233     if ( !_cmd )
00234       ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
00235 
00236     if ( frame_r.command().empty() )
00237       WAR << "Send: No command in frame" << frame_r << endl;
00238 
00239     // prepare frame data to write
00240     std::string data;
00241     {
00242       std::ostringstream datas;
00243       frame_r.writeTo( datas );
00244       datas.str().swap( data );
00245     }
00246     DBG << "->send " << frame_r << endl;
00247 
00248     if ( PLUGIN_DEBUG )
00249     {
00250       std::istringstream datas( data );
00251       iostr::copyIndent( datas, _DBG("PLUGIN") ) << endl;
00252     }
00253 
00254     // try writing the pipe....
00255     FILE * filep = _cmd->outputFile();
00256     if ( ! filep )
00257       ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
00258 
00259     int fd = ::fileno( filep );
00260     if ( fd == -1 )
00261       ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
00262 
00263     //DBG << " ->[" << fd << " " << (::feof(filep)?'e':'_') << (::ferror(filep)?'F':'_') << "]" << endl;
00264     {
00265       PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
00266       SignalSaver sigsav( SIGPIPE, SIG_IGN );
00267       const char * buffer = data.c_str();
00268       ssize_t buffsize = data.size();
00269       do {
00270         fd_set wfds;
00271         FD_ZERO( &wfds );
00272         FD_SET( fd, &wfds );
00273 
00274         struct timeval tv;
00275         tv.tv_sec = send_timeout;
00276         tv.tv_usec = 0;
00277 
00278         int retval = select( fd+1, NULL, &wfds, NULL, &tv );
00279         if ( retval > 0 )       // FD_ISSET( fd, &wfds ) will be true.
00280         {
00281           //DBG << "Ready to write..." << endl;
00282           ssize_t ret = ::write( fd, buffer, buffsize );
00283           if ( ret == buffsize )
00284           {
00285             //DBG << "::write(" << buffsize << ") -> " << ret << endl;
00286             ::fflush( filep );
00287             break;              // -> done
00288           }
00289           else if ( ret > 0 )
00290           {
00291             //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl;
00292             ::fflush( filep );
00293             buffsize -= ret;
00294             buffer += ret;      // -> continue
00295           }
00296           else // ( retval == -1 )
00297           {
00298             if ( errno != EINTR )
00299             {
00300               ERR << "write(): " << Errno() << endl;
00301               if ( errno == EPIPE )
00302                 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", str::Str() << Errno() ) );
00303               else
00304                 ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) );
00305             }
00306           }
00307         }
00308         else if ( retval == 0 )
00309         {
00310           WAR << "Not ready to write within timeout." << endl;
00311           ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) );
00312         }
00313         else // ( retval == -1 )
00314         {
00315           if ( errno != EINTR )
00316           {
00317             ERR << "select(): " << Errno() << endl;
00318             ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
00319           }
00320         }
00321       } while( true );
00322     }
00323   }
00324 
00325   PluginFrame PluginScript::Impl::receive() const
00326   {
00327     if ( !_cmd )
00328       ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
00329 
00330     // try reading the pipe....
00331     FILE * filep = _cmd->inputFile();
00332     if ( ! filep )
00333       ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
00334 
00335     int fd = ::fileno( filep );
00336     if ( fd == -1 )
00337       ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
00338 
00339     ::clearerr( filep );
00340     std::string data;
00341     {
00342       PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG
00343       PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
00344       do {
00345         int ch = fgetc( filep );
00346         if ( ch != EOF )
00347         {
00348           data.push_back( ch );
00349           if ( ch == '\0' )
00350             break;
00351         }
00352         else if ( ::feof( filep ) )
00353         {
00354           WAR << "Unexpected EOF" << endl;
00355           ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", str::Str() << Errno() ) );
00356         }
00357         else if ( errno != EINTR )
00358         {
00359           if ( errno == EWOULDBLOCK )
00360           {
00361             // wait a while for fd to become ready for reading...
00362             fd_set rfds;
00363             FD_ZERO( &rfds );
00364             FD_SET( fd, &rfds );
00365 
00366             struct timeval tv;
00367             tv.tv_sec = receive_timeout;
00368             tv.tv_usec = 0;
00369 
00370             int retval = select( fd+1, &rfds, NULL, NULL, &tv );
00371             if ( retval > 0 )   // FD_ISSET( fd, &rfds ) will be true.
00372             {
00373               ::clearerr( filep );
00374             }
00375             else if ( retval == 0 )
00376             {
00377               WAR << "Not ready to read within timeout." << endl;
00378               ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) );
00379             }
00380             else // ( retval == -1 )
00381             {
00382               if ( errno != EINTR )
00383               {
00384                 ERR << "select(): " << Errno() << endl;
00385                 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
00386               }
00387             }
00388           }
00389           else
00390           {
00391             ERR << "read(): " << Errno() << endl;
00392             ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) );
00393           }
00394         }
00395       } while ( true );
00396     }
00397     DBG << " <-read " << data.size() << endl;
00398     std::istringstream datas( data );
00399     PluginFrame ret( datas );
00400     DBG << ret << endl;
00401     return ret;
00402   }
00403 
00405   //
00406   //    CLASS NAME : PluginScript
00407   //
00409 
00410   const pid_t PluginScript::NotConnected( -1 );
00411 
00412   PluginScript::PluginScript()
00413     : _pimpl( new Impl )
00414   {}
00415 
00416   PluginScript::PluginScript( const Pathname & script_r )
00417     : _pimpl( new Impl( script_r ) )
00418   {}
00419 
00420   PluginScript::PluginScript( const Pathname & script_r, const Arguments & args_r )
00421     : _pimpl( new Impl( script_r, args_r ) )
00422   {}
00423 
00424   const Pathname & PluginScript::script() const
00425   { return _pimpl->script(); }
00426 
00427   const PluginScript::Arguments & PluginScript::args() const
00428   { return _pimpl->args(); }
00429 
00430   bool PluginScript::isOpen() const
00431   { return _pimpl->isOpen(); }
00432 
00433   pid_t PluginScript::getPid() const
00434   { return _pimpl->getPid(); }
00435 
00436   int PluginScript::lastReturn() const
00437   { return _pimpl->lastReturn(); }
00438 
00439   const std::string & PluginScript::lastExecError() const
00440   { return _pimpl->lastExecError(); }
00441 
00442   void PluginScript::open()
00443   { _pimpl->open( _pimpl->script(), _pimpl->args() ); }
00444 
00445   void PluginScript::open( const Pathname & script_r )
00446   { _pimpl->open( script_r ); }
00447 
00448   void PluginScript::open( const Pathname & script_r, const Arguments & args_r )
00449   { _pimpl->open( script_r, args_r ); }
00450 
00451   int PluginScript::close()
00452   { return _pimpl->close(); }
00453 
00454   void PluginScript::send( const PluginFrame & frame_r ) const
00455   { _pimpl->send( frame_r ); }
00456 
00457   PluginFrame PluginScript::receive() const
00458   { return _pimpl->receive(); }
00459 
00461 
00462   std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
00463   { return str << *obj._pimpl; }
00464 
00466 } // namespace zypp

doxygen