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