00001
00002
00003
00004
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
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
00203
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
00211 setNonBlocking( _cmd->outputFile() );
00212 setNonBlocking( _cmd->inputFile() );
00213
00214
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
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 { }
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
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
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
00288 {
00289 PluginDumpStderr _dump( *_cmd );
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 )
00304 {
00305
00306 ssize_t ret = ::write( fd, buffer, buffsize );
00307 if ( ret == buffsize )
00308 {
00309
00310 ::fflush( filep );
00311 break;
00312 }
00313 else if ( ret > 0 )
00314 {
00315
00316 ::fflush( filep );
00317 buffsize -= ret;
00318 buffer += ret;
00319 }
00320 else
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
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
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 );
00367 PluginDumpStderr _dump( *_cmd );
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
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 )
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
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
00422 std::istringstream datas( data );
00423 PluginFrame ret( datas );
00424 DBG << "<-" << ret << endl;
00425 return ret;
00426 }
00427
00429
00430
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 }